Пример #1
0
 def param(raw):
     parts = raw.split("=", 1)
     if len(parts) == 1:
         return "%s: null" % jsify(decodeURIComponent(raw))
     else:
         return "%s: %s" % (jsify(decodeURIComponent(
             parts[0])), jsify(decodeURIComponent(parts[1])))
Пример #2
0
 def param(raw):
     parts = raw.split("=", 1)
     if len(parts) == 1:
         return "%s: null" % jsify(decodeURIComponent(raw))
     else:
         return "%s: %s" % (jsify(decodeURIComponent(parts[0])),
                            jsify(decodeURIComponent(parts[1])))
Пример #3
0
    def getJSConstructor(self):
        from htmlutils import jsify

        if self.base:
            return "new Branch(%d, %s, %s)" % (self.id, jsify(self.name), self.base.getJSConstructor())
        else:
            return "new Branch(%d, %s, null)" % (self.id, jsify(self.name))
Пример #4
0
        def render(target):
            table = target.table("createuser", align="center")

            def header(label):
                row = table.tr("header")
                row.td(colspan=2).text(label)

            def item(key):
                row = table.tr("item")
                row.td("key").text("%s:" % key)
                return row.td("value")

            def button(class_name):
                row = table.tr("button")
                return row.td(colspan=2).button(class_name)

            def separator():
                table.tr("separator1").td(colspan=2)
                table.tr("separator2").td(colspan=2)

            if provider:
                self.document.addInternalScript(
                    "var external = { provider: %s, account: %s, token: %s };"
                    % (htmlutils.jsify(self.provider),
                       htmlutils.jsify(self.account),
                       htmlutils.jsify(self.token)))

                url = provider.getAccountURL(self.account)
                item(provider.getTitle()).a("external", href=url).text(self.account)
                separator()
            else:
                self.document.addInternalScript("var external = null;")

            message = table.tr("status disabled").td(colspan=2).div("message")

            if self.username:
                try:
                    dbutils.User.fromName(self.db, self.username)
                except dbutils.NoSuchUser:
                    try:
                        auth.validateUserName(self.username)
                    except auth.InvalidUserName as error:
                        message.u("Invalid user name")
                        message.br()
                        message.text(str(error))
                else:
                    message.text("A user named '%s' already exists!"
                                 % self.username)

            item("New user name").input(id="newusername", value=self.username, size=40)
            item("Display name").input(id="fullname", value=self.fullname, size=40)
            item("Email").input(id="email", value=self.email, size=40)

            if not provider:
                separator()

                item("Password").input(id="password1", type="password", size=40)
                item("Password (again)").input(id="password2", type="password", size=40)

            button("create").text("Create user")
Пример #5
0
    def getJSConstructor(self, sha1=None):
        if self.closed_by:
            closed_by = self.closed_by.getJSConstructor()
        else:
            closed_by = "null"

        if self.addressed_by:
            addressed_by = jsify(self.addressed_by.sha1)
        else:
            addressed_by = "null"

        comments = ", ".join(map(Comment.getJSConstructor, self.comments))

        if sha1:
            offset, count = self.lines_by_sha1[sha1]
            if self.file_id:
                lines = "new CommentLines(%d, %s, %d, %d)" % (self.file_id, jsify(sha1), offset, offset + count - 1)
            else:
                lines = "new CommentLines(null, %s, %d, %d)" % (jsify(sha1), offset, offset + count - 1)
        else:
            lines = "null"

        return "new CommentChain(%d, %s, %s, %s, %s, %s, %s, [%s], %s)" % (
            self.id,
            self.user.getJSConstructor(),
            jsify(self.type),
            "true" if self.type_is_draft else "false",
            jsify(self.state),
            closed_by,
            addressed_by,
            comments,
            lines,
        )
Пример #6
0
    def getJSConstructor(self, db=None):
        from htmlutils import jsify

        if self.isAnonymous():
            return "new User(null, null, null, null, null, { ui: {} })"
        if db:
            options = (
                "{ ui: { keyboardShortcuts: %s, resolveIssueWarning: %s, convertIssueToNote: %s, asynchronousReviewMarking: %s } }"
                % (
                    "true" if self.getPreference(db, "ui.keyboardShortcuts") else "false",
                    "true" if self.getPreference(db, "ui.resolveIssueWarning") else "false",
                    "true" if self.getPreference(db, "ui.convertIssueToNote") else "false",
                    "true" if self.getPreference(db, "ui.asynchronousReviewMarking") else "false",
                )
            )
        else:
            options = "{ ui: {} }"
        return "new User(%d, %s, %s, %s, %s, %s)" % (
            self.id,
            jsify(self.name),
            jsify(self.email),
            jsify(self.fullname),
            jsify(self.status),
            options,
        )
Пример #7
0
 def getJSConstructor(self):
     return "new Comment(%d, %s, %s, %s, %s)" % (
         self.id,
         self.user.getJSConstructor(),
         jsify(strftime("%Y-%m-%d %H:%M", self.time.timetuple())),
         jsify(self.state),
         jsify(self.comment),
     )
Пример #8
0
 def getJSConstructor(self, db=None):
     from htmlutils import jsify
     if self.isAnonymous():
         return "new User(null, null, null, null, null, { ui: {} })"
     if db:
         options = ("{ ui: { keyboardShortcuts: %s, resolveIssueWarning: %s, convertIssueToNote: %s, asynchronousReviewMarking: %s } }" %
                    ("true" if self.getPreference(db, "ui.keyboardShortcuts") else "false",
                     "true" if self.getPreference(db, "ui.resolveIssueWarning") else "false",
                     "true" if self.getPreference(db, "ui.convertIssueToNote") else "false",
                     "true" if self.getPreference(db, "ui.asynchronousReviewMarking") else "false"))
     else:
         options = "{ ui: {} }"
     return "new User(%d, %s, %s, %s, %s, %s)" % (self.id, jsify(self.name), jsify(self.email), jsify(self.fullname), jsify(self.status), options)
Пример #9
0
    def generateHeader(self):
        self.document.addExternalStylesheet("resource/createuser.css")
        self.document.addExternalScript("resource/createuser.js")

        if self.target:
            self.document.addInternalScript(
                "var target = %s;" % htmlutils.jsify(self.target))
Пример #10
0
    def render(self, db, commit, target, overrides={}):
        summary = overrides.get("summary", commit.summary())
        classnames = ["commit"] + overrides.get("summary_classnames", [])

        if self.isFixupOrSquash is not None:
            data = self.isFixupOrSquash(commit)
            if data:
                what, ref_commit = data
                target.span(what, critic_ref=ref_commit.summary()).text("[%s] " % what)
                lines = commit.message.splitlines()[1:]
                while lines and not lines[0].strip():
                    lines.pop(0)
                if lines:
                    summary = lines[0]
                else:
                    summary = None
                if not summary:
                    classnames.append("nocomment")
                    summary = "(no comment)"

        url = self.linkToCommit(commit, overrides)

        if summary:
            target.a(" ".join(classnames), href=url).text(summary)

        target.setAttribute("onclick", "location.href=%s; return false;" % jsify(url))
Пример #11
0
    def render(self, db, commit, target):
        summary = commit.summary()
        classname = "commit"

        if self.isFixupOrSquash is not None:
            data = self.isFixupOrSquash(commit)
            if data:
                what, ref_commit = data
                target.span(what, critic_ref=ref_commit.summary()).text(
                    "[%s] " % what)
                lines = commit.message.splitlines()[1:]
                while lines and not lines[0].strip():
                    lines.pop(0)
                if lines: summary = lines[0]
                else: summary = None
                if not summary:
                    classname += " nocomment"
                    summary = "(no comment)"

        url = self.linkToCommit(commit)

        if summary:
            target.a(classname, href=url).text(summary)

        target.setAttribute("onclick", "location.href=%s;" % jsify(url))
Пример #12
0
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
Пример #13
0
 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")
Пример #14
0
    def renderBranchName(target):
        target.code("branch").text(review.branch.name, linkify=linkify.Context())

        if repository.name != user.getPreference(db, "defaultRepository"):
            target.text(" in ")
            target.code("repository").text(repository.getURL(db, user))

        cursor.execute("""SELECT id, remote, remote_name, disabled, previous
                            FROM trackedbranches
                           WHERE repository=%s
                             AND local_name=%s""",
                       (repository.id, review.branch.name))

        row = cursor.fetchone()
        if row:
            trackedbranch_id, remote, remote_name, disabled, previous = row

            target.p("tracking disabled" if disabled else "tracking").text("tracking")

            target.code("branch").text(remote_name, linkify=linkify.Context(remote=remote))
            target.text(" in ")
            target.code("repository").text(remote, linkify=linkify.Context())

            if previous:
                target.span("lastupdate").script(type="text/javascript").text("document.write('(last fetched: ' + shortDate(new Date(%d)) + ')');" % (calendar.timegm(previous.utctimetuple()) * 1000))

            if user in review.owners or user.hasRole(db, "administrator"):
                buttons = target.div("buttons")

                if review.state == "open":
                    if disabled:
                        button = buttons.button("enabletracking",
                                                onclick=("enableTracking(%d, %s, %s);"
                                                         % (trackedbranch_id,
                                                            htmlutils.jsify(remote),
                                                            htmlutils.jsify(remote_name))))
                        button.text("Enable Tracking")
                    else:
                        buttons.button("disabletracking", onclick="triggerUpdate(%d);" % trackedbranch_id).text("Update Now")
                        buttons.button("disabletracking", onclick="disableTracking(%d);" % trackedbranch_id).text("Disable Tracking")

                    buttons.button("rebasereview", onclick="location.assign('/rebasetrackingreview?review=%d');" % review.id).text("Rebase Review")
Пример #15
0
    def getJSConstructor(self, sha1=None):
        if self.closed_by: closed_by = self.closed_by.getJSConstructor()
        else: closed_by = "null"

        if self.addressed_by: addressed_by = jsify(self.addressed_by.sha1)
        else: addressed_by = "null"

        comments = ", ".join(map(Comment.getJSConstructor, self.comments))

        if sha1:
            offset, count = self.lines_by_sha1[sha1]
            if self.file_id:
                lines = "new CommentLines(%d, %s, %d, %d)" % (
                    self.file_id, jsify(sha1), offset, offset + count - 1)
            else:
                lines = "new CommentLines(null, %s, %d, %d)" % (
                    jsify(sha1), offset, offset + count - 1)
        else:
            lines = "null"

        return "new CommentChain(%d, %s, %s, %s, %s, %s, %s, [%s], %s)" % (
            self.id, self.user.getJSConstructor(), jsify(
                self.type), "true" if self.type_is_draft else "false",
            jsify(self.state), closed_by, addressed_by, comments, lines)
Пример #16
0
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
Пример #17
0
    def render(self, db, commit, target):
        summary = commit.summary()
        classname = "commit"

        if self.isFixupOrSquash is not None:
            data = self.isFixupOrSquash(commit)
            if data:
                what, ref_commit = data
                target.span(what, critic_ref=ref_commit.summary()).text("[%s] " % what)
                lines = commit.message.splitlines()[1:]
                while lines and not lines[0].strip():
                    lines.pop(0)
                if lines: summary = lines[0]
                else: summary = None
                if not summary:
                    classname += " nocomment"
                    summary = "(no comment)"

        url = self.linkToCommit(commit)

        if summary:
            target.a(classname, href=url).text(summary)

        target.setAttribute("onclick", "location.href=%s;" % jsify(url))
Пример #18
0
 def getJS(self):
     return "var repository = critic.repository = new Repository(%d, %s, %s);" % (
         self.id, htmlutils.jsify(self.name), htmlutils.jsify(self.path))
Пример #19
0
def renderManageExtensions(req, db, user):
    cursor = db.cursor()

    what = page.utils.getParameter(req, "what", "available")
    selected_versions = page.utils.json_decode(page.utils.getParameter(req, "select", "{}"))
    focused = page.utils.getParameter(req, "focus", None)

    if what == "available":
        title = "Available Extensions"
        other = ("installed extensions", "/manageextensions?what=installed" + ("&user="******""))
        listed_extensions = Extension.find()
    elif what == "installed":
        title = "Installed Extensions"
        other = ("available extensions", "/manageextensions?what=available" + ("&user="******""))
        cursor.execute("""SELECT DISTINCT users.name, extensions.name
                            FROM users
                            JOIN extensions ON (extensions.author=users.id)
                            JOIN extensionversions ON (extensionversions.extension=extensions.id)
                 LEFT OUTER JOIN extensionroles_page ON (extensionroles_page.version=extensionversions.id AND extensionroles_page.uid=%s)
                 LEFT OUTER JOIN extensionroles_processcommits ON (extensionroles_processcommits.version=extensionversions.id AND extensionroles_processcommits.uid=%s)
                           WHERE extensionroles_page.uid IS NOT NULL
                              OR extensionroles_processcommits.uid IS NOT NULL""",
                       (user.id, user.id))
        listed_extensions = [Extension(*row) for row in cursor]

    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)
    table.titleRight.a(href=other[1]).text("[" + other[0] + "]")

    for extension in listed_extensions:
        extension_path = extension.getPath()
        author = dbutils.User.fromName(db, extension.getAuthorName())

        if focused and extension.getKey() != focused:
            continue

        selected_version = selected_versions.get(extension.getKey(), False)
        installed_sha1, installed_version = extension.getInstalledVersion(db, user)

        if selected_version is False:
            selected_version = installed_version

        if selected_version is None: install_version = "live"
        elif selected_version is not False: install_version = "version/%s" % selected_version
        else: install_version = None

        try:
            if selected_version is False:
                manifest = extension.readManifest()
            else:
                manifest = extension.getInstallationStatus(db, user, selected_version)
        except ManifestError, error:
            manifest = None

        if installed_sha1:
            current_sha1 = extension.getCurrentSHA1(installed_version)

        if manifest:
            if what == "available" and author != user and manifest.hidden: continue
        else:
            if author != user: continue

        if manifest:
            buttons = []

            if installed_version is False:
                if install_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 installed_sha1 and installed_sha1 != current_sha1: label = "Update"
                elif manifest.status != "installed": label = "Reinstall"
                else: label = None

                if label: buttons.append((label, "reinstallExtension(%s, %s, %s)" % (htmlutils.jsify(extension.getAuthorName()), htmlutils.jsify(extension.getName()), htmlutils.jsify(install_version))))
        else:
            buttons = None

        def renderItem(target):
            span = target.span("name")
            span.b().text(extension.getName())
            span.text(" by %s" % author.fullname)

            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 extension.getVersions():
                    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 = manifest.status in ("partial", "installed") or installed_version is not False

                if is_installed:
                    target.span("installed").text(" [installed]")

                target.div("description").preformatted().text(manifest.description, linkify=True)
            else:
                is_installed = False

                target.div("description broken").preformatted().a(href="loadmanifest?author=%s&name=%s" % (extension.getAuthorName(), extension.getName())).text("[This extension has an invalid MANIFEST file]")

            if selected_version is False:
                return

            pages = []
            injects = []
            processcommits = []
            processchanges = []
            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, ProcessChangesRole):
                        processchanges.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), role.pattern)
                    if is_installed and role.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 is_installed and not role.installed:
                        td.text(" ")
                        td.span("inactive").text("[Not active!]")

            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), role.pattern))
                    td = row.td("description")
                    td.text(role.description)

                    if is_installed and not role.installed:
                        td.text(" ")
                        td.span("inactive").text("[Not active!]")

            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 is_installed and not role.installed:
                        li.text(" ")
                        li.span("inactive").text("[Not active!]")

            if processchanges:
                role_table.tr().th(colspan=2).text("ProcessChanges hooks")
                ul = role_table.tr().td(colspan=2).ul()

                for role in processchanges:
                    li = ul.li()
                    li.text(role.description)

                    if is_installed and not role.installed:
                        li.text(" ")
                        li.span("inactive").text("[Not active!]")

            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)

                    if is_installed and not role.installed:
                        td.text(" ")
                        td.span("inactive").text("[Not active!]")

        cursor.execute("""SELECT DISTINCT uid
                            FROM extensionroles
                            JOIN extensionversions ON (extensionversions.id=extensionroles.version)
                            JOIN extensions ON (extensions.id=extensionversions.extension)
                           WHERE extensions.author=%s
                             AND extensions.name=%s""",
                       (author.id, extension.getName()))

        installed_count = len(cursor.fetchall())

        if installed_count: installed = " (installed by %d user%s)" % (installed_count, "s" if installed_count > 1 else "")
        else: installed = ""

        table.addItem("Extension", renderItem, extension_path + "/" + installed, buttons)
Пример #20
0
def renderConfig(req, db, user):
    highlight = req.getParameter("highlight", None)
    repository = req.getParameter("repository", None,
                                  gitutils.Repository.FromParameter(db))
    filter_id = req.getParameter("filter", None, int)
    defaults = req.getParameter("defaults", "no") == "yes"

    if filter_id is not None:
        # There can't be system-wide defaults for one of a single user's
        # filters.
        defaults = False

    cursor = db.cursor()

    if filter_id is not None:
        cursor.execute(
            """SELECT filters.path, repositories.name
                            FROM filters
                            JOIN repositories ON (repositories.id=filters.repository)
                           WHERE filters.id=%s""", (filter_id, ))
        row = cursor.fetchone()
        if not row:
            raise page.utils.InvalidParameterValue(name="filter",
                                                   value=str(filter_id),
                                                   expected="valid filter id")
        title = "Filter preferences: %s in %s" % row
    elif repository is not None:
        title = "Repository preferences: %s" % repository.name
    else:
        title = "User preferences"

    document = htmlutils.Document(req)
    document.setTitle(title)

    html = document.html()
    head = html.head()
    body = html.body()

    if user.isAnonymous():
        disabled = "disabled"
    else:
        disabled = None

    def generate_right(target):
        if defaults:
            url = "/config"
            if repository is not None:
                url += "?repository=%d" % repository.id
            target.a("button", href=url).text("Edit Own")
        elif user.hasRole(db, "administrator"):
            url = "/config?defaults=yes"
            if repository is not None:
                url += "&repository=%d" % repository.id
                what = "Repository Defaults"
            else:
                what = "System Defaults"
            target.a("button", href=url).text("Edit " + what)

    injected = page.utils.generateHeader(body,
                                         db,
                                         user,
                                         current_page="config",
                                         generate_right=generate_right)

    document.addExternalStylesheet("resource/config.css")
    document.addExternalScript("resource/config.js")
    document.addInternalScript(user.getJS())
    document.addInternalScript(
        "var repository_id = %s, filter_id = %s, defaults = %s;" %
        (htmlutils.jsify(repository.id if repository else None),
         htmlutils.jsify(filter_id), htmlutils.jsify(defaults)))

    target = body.div("main")

    table = target.table('preferences paleyellow',
                         align='center',
                         cellspacing=0)
    h1 = table.tr().td('h1', colspan=3).h1()
    h1.text(title)

    if filter_id is None:
        page.utils.generateRepositorySelect(
            db,
            user,
            h1.span("right"),
            allow_selecting_none=True,
            selected=repository.name if repository else False)

    if filter_id is not None:
        conditional = "per_filter"
    elif repository is not None:
        conditional = "per_repository"
    elif defaults:
        conditional = "per_system"
    else:
        conditional = "per_user"

    cursor = db.cursor()
    cursor.execute(
        """SELECT item, type, description, per_repository, per_filter
                        FROM preferences
                       WHERE %(conditional)s""" % {"conditional": conditional})

    preferences = dict((
        item,
        [preference_type, description, None, None, per_repository, per_filter])
                       for item, preference_type, description, per_repository,
                       per_filter in cursor)

    def set_values(rows, is_overrides):
        index = 3 if is_overrides else 2
        for item, integer, string in rows:
            if preferences[item][0] == "boolean":
                preferences[item][index] = bool(integer)
            elif preferences[item][0] == "integer":
                preferences[item][index] = integer
            else:
                preferences[item][index] = string

    cursor.execute(
        """SELECT item, integer, string
                        FROM userpreferences
                       WHERE item=ANY (%s)
                         AND uid IS NULL
                         AND repository IS NULL""", (preferences.keys(), ))

    set_values(cursor, is_overrides=False)

    if repository is not None:
        cursor.execute(
            """SELECT item, integer, string
                            FROM userpreferences
                           WHERE item=ANY (%s)
                             AND uid IS NULL
                             AND repository=%s""",
            (preferences.keys(), repository.id))

        # These are overrides if we're editing the defaults for a specific
        # repository.
        set_values(cursor, is_overrides=defaults)

    if not defaults:
        cursor.execute(
            """SELECT item, integer, string
                            FROM userpreferences
                           WHERE item=ANY (%s)
                             AND uid=%s
                             AND repository IS NULL
                             AND filter IS NULL""",
            (preferences.keys(), user.id))

        if filter_id is not None or repository is not None:
            # We're looking at per-filter or per-repository settings, so the
            # user's global settings are defaults, not the overrides.  If a
            # per-filter or per-repository override is deleted, the user's
            # global setting kicks in instead.
            set_values(cursor, is_overrides=False)

            if filter_id is not None:
                cursor.execute(
                    """SELECT item, integer, string
                                    FROM userpreferences
                                   WHERE item=ANY (%s)
                                     AND uid=%s
                                     AND filter=%s""",
                    (preferences.keys(), user.id, filter_id))
            else:
                cursor.execute(
                    """SELECT item, integer, string
                                    FROM userpreferences
                                   WHERE item=ANY (%s)
                                     AND uid=%s
                                     AND repository=%s""",
                    (preferences.keys(), user.id, repository.id))

        # Set the overrides.  This is either the user's global settings, if
        # we're not looking at per-filter or per-repository settings, or the
        # user's per-filter or per-repository settings if we are.
        set_values(cursor, is_overrides=True)
    elif repository is None:
        # When editing global defaults, use the values from preferences.json
        # used when initially installing Critic as the default values.
        defaults_path = os.path.join(configuration.paths.INSTALL_DIR,
                                     "data/preferences.json")
        with open(defaults_path) as defaults_file:
            factory_defaults = textutils.json_decode(defaults_file.read())
        for item, data in preferences.items():
            data[3] = data[2]
            if item in factory_defaults:
                data[2] = factory_defaults[item]["default"]
                if data[2] == data[3]:
                    data[3] = None

    if req.getParameter("recalculate", "no") == "yes":
        for item, data in preferences.items():
            if data[2] == data[3]:
                user.setPreference(db,
                                   item,
                                   None,
                                   repository=repository,
                                   filter_id=filter_id)
                data[3] = None
        db.commit()

    debug_enabled = user.getPreference(db, "debug.enabled")

    for item, (preference_type, description, default_value, current_value,
               per_repository, per_filter) in sorted(preferences.items()):
        if item.startswith(
                "debug.") and item != "debug.enabled" and not debug_enabled:
            continue

        line_class_name = "line"
        help_class_name = "help"

        if highlight is not None and not fnmatch.fnmatch(item, highlight):
            continue

        if current_value is None:
            current_value = default_value
        else:
            line_class_name += " customized"

        row = table.tr(line_class_name)
        heading = row.td("heading")
        heading.text("%s:" % item)
        value = row.td("value", colspan=2)
        value.preformatted()

        options = None
        optgroup = None

        def addOption(value,
                      name,
                      selected=lambda value: value == current_value,
                      **attributes):
            (optgroup or options).option(
                value=value,
                selected="selected" if selected(value) else None,
                **attributes).text(name)

        if preference_type == "boolean":
            value.input("setting",
                        type="checkbox",
                        name=item,
                        checked="checked" if current_value else None,
                        disabled=disabled,
                        critic_current=htmlutils.jsify(current_value),
                        critic_default=htmlutils.jsify(default_value))
        elif preference_type == "integer":
            value.input("setting",
                        type="number",
                        min=0,
                        max=2**31 - 1,
                        name=item,
                        value=current_value,
                        disabled=disabled,
                        critic_current=htmlutils.jsify(current_value),
                        critic_default=htmlutils.jsify(default_value))
        elif item == "defaultRepository":
            page.utils.generateRepositorySelect(
                db,
                user,
                value,
                allow_selecting_none=True,
                placeholder_text="No default repository",
                selected=current_value,
                name=item,
                disabled=disabled,
                critic_current=htmlutils.jsify(current_value),
                critic_default=htmlutils.jsify(default_value))
        elif item == "defaultPage":
            options = value.select(
                "setting",
                name=item,
                disabled=disabled,
                critic_current=htmlutils.jsify(current_value),
                critic_default=htmlutils.jsify(default_value))

            addOption("home", "Home")
            addOption("dashboard", "Dashboard")
            addOption("branches", "Branches")
            addOption("config", "Config")
            addOption("tutorial", "Tutorial")
        elif item == "email.urlType":
            cursor2 = db.cursor()
            cursor2.execute(
                """SELECT key, description, authenticated_scheme, hostname
                                 FROM systemidentities
                             ORDER BY description ASC""")

            identities = cursor2.fetchall()
            selected = set(current_value.split(","))

            options = value.select(
                "setting",
                name=item,
                size=len(identities),
                multiple="multiple",
                disabled=disabled,
                critic_current=htmlutils.jsify(current_value),
                critic_default=htmlutils.jsify(default_value))

            for key, label, authenticated_scheme, hostname in identities:
                prefix = "%s://%s/" % (authenticated_scheme, hostname)
                addOption(
                    key,
                    label,
                    selected=lambda value: value in selected,
                    class_="url-type flex",
                    data_text=label,
                    data_html=(
                        "<span class=label>%s</span>"
                        "<span class=prefix>%s</span>" %
                        (htmlutils.htmlify(label), htmlutils.htmlify(prefix))))

        elif item == "email.updatedReview.quotedComments":
            options = value.select(
                "setting",
                name=item,
                disabled=disabled,
                critic_current=htmlutils.jsify(current_value),
                critic_default=htmlutils.jsify(default_value))

            addOption("all", "All")
            addOption("first", "First")
            addOption("last", "Last")
            addOption("firstlast", "First & Last")
        elif item == "timezone":
            options = value.select(
                "setting",
                name=item,
                disabled=disabled,
                critic_current=htmlutils.jsify(current_value),
                critic_default=htmlutils.jsify(default_value))

            for group, zones in dbutils.timezones.sortedTimezones(db):
                optgroup = options.optgroup(label=group)
                for name, abbrev, utc_offset in zones:
                    seconds = utc_offset.total_seconds()
                    offset = "%s%02d:%02d" % ("-" if seconds < 0 else "+",
                                              abs(seconds) / 3600,
                                              (abs(seconds) % 3600) / 60)
                    addOption("%s/%s" % (group, name),
                              "%s (%s / UTC%s)" % (name, abbrev, offset))
        elif item == "repository.urlType":
            options = value.select(
                "setting",
                name=item,
                disabled=disabled,
                critic_current=htmlutils.jsify(current_value),
                critic_default=htmlutils.jsify(default_value))
            long_path = os.path.join(configuration.paths.GIT_DIR, "<path>.git")

            if "git" in configuration.base.REPOSITORY_URL_TYPES:
                addOption("git",
                          "git://%s/<path>.git" % configuration.base.HOSTNAME)
            if "http" in configuration.base.REPOSITORY_URL_TYPES:
                scheme = configuration.base.ACCESS_SCHEME
                if scheme == "both":
                    if user.isAnonymous():
                        scheme = "http"
                    else:
                        scheme = "https"
                addOption(
                    "http", "%s://%s/<path>.git" %
                    (scheme, configuration.base.HOSTNAME))
            if "ssh" in configuration.base.REPOSITORY_URL_TYPES:
                addOption(
                    "ssh",
                    "ssh://%s%s" % (configuration.base.HOSTNAME, long_path))
            if "host" in configuration.base.REPOSITORY_URL_TYPES:
                addOption("host",
                          "%s:%s" % (configuration.base.HOSTNAME, long_path))
        else:
            if item.startswith("email.subjectLine."):
                placeholder = "Email type disabled"
            else:
                placeholder = None
            value.input("setting",
                        type="text",
                        size=80,
                        name=item,
                        placeholder=placeholder,
                        value=current_value,
                        disabled=disabled,
                        critic_current=htmlutils.jsify(current_value),
                        critic_default=htmlutils.jsify(default_value))

        also_configurable_per = []

        if per_repository and repository is None:
            also_configurable_per.append("repository")
        if per_filter and filter_id is None:
            also_configurable_per.append("filter")

        if also_configurable_per:
            value.span("also-configurable-per").text(
                "Also configurable per: %s" % ", ".join(also_configurable_per))

        reset = value.span("reset")
        reset.a(href="javascript:saveSettings(%s);" %
                htmlutils.jsify(item)).text("[reset to default]")

        cell = table.tr(help_class_name).td("help", colspan=3)

        magic_description_links = {
            "format string for subject line":
            "/tutorial?item=reconfigure#subject_line_formats",
            "phony recipients":
            "/tutorial?item=reconfigure#review_association_recipients"
        }

        for link_text, link_href in magic_description_links.items():
            prefix, link_text, suffix = description.partition(link_text)
            if link_text:
                cell.text(prefix)
                cell.a(href=link_href).text(link_text)
                cell.text(suffix)
                break
        else:
            cell.text(description)

    if injected and injected.has_key("preferences") \
            and not defaults \
            and repository is None \
            and filter_id is None:
        for extension_name, author, preferences in injected["preferences"]:
            if highlight is not None:
                prefix = "%s/%s" % (author.name, extension_name)
                preferences = [
                    preference for preference in preferences
                    if fnmatch.fnmatch("%s/%s" %
                                       (prefix, preference["name"]), highlight)
                ]

                if not preferences:
                    continue

            h2 = table.tr("extension").td("extension", colspan=3).h2()
            h2.span("name").text(extension_name)
            h2.text(" by ")
            h2.span("author").text(author.fullname)

            for preference in preferences:
                preference_url = preference["url"]
                preference_name = preference["name"]
                preference_type = preference["type"]
                preference_value = preference["value"]
                preference_default = preference["default"]
                preference_description = preference["description"]

                line_class_name = "line"
                help_class_name = "help"

                if preference_value != preference_default:
                    line_class_name += " customized"

                row = table.tr(line_class_name)
                heading = row.td("heading")
                heading.text("%s:" % preference_name)
                value = row.td("value", colspan=2)
                value.preformatted()

                if preference_type == "boolean":
                    value.input(
                        "setting",
                        type="checkbox",
                        name=preference_name,
                        disabled=disabled,
                        checked="checked" if preference_value else None,
                        critic_url=preference_url,
                        critic_default=htmlutils.jsify(bool(preference_value)),
                        critic_extension=extension_name)
                elif preference_type == "integer":
                    value.input(
                        "setting",
                        type="number",
                        min=0,
                        name=preference_name,
                        value=preference_value,
                        disabled=disabled,
                        critic_url=preference_url,
                        critic_default=htmlutils.jsify(preference_default),
                        critic_extension=extension_name)
                elif preference_type == "string":
                    value.input(
                        "setting",
                        type="text",
                        name=preference_name,
                        value=preference_value,
                        disabled=disabled,
                        critic_url=preference_url,
                        critic_default=htmlutils.jsify(preference_default),
                        critic_extension=extension_name)
                else:
                    select = value.select(
                        "setting",
                        name=preference_name,
                        disabled=disabled,
                        critic_url=preference_url,
                        critic_value=preference_value,
                        critic_default=htmlutils.jsify(preference_default),
                        critic_extension=extension_name)

                    for choice in preference_type:
                        select.option(value=choice["value"],
                                      selected="selected" if preference_value
                                      == choice["value"] else None).text(
                                          choice["title"])

                cell = table.tr(help_class_name).td("help", colspan=3)
                cell.text(preference_description)

    critic_installed_sha1 = dbutils.getInstalledSHA1(db)
    div = body.div("installed_sha1")
    div.text("Critic version: ")
    div.a(href="https://critic-review.org/critic/%s" %
          critic_installed_sha1).text(critic_installed_sha1)

    return document
Пример #21
0
    def render(target):
        table = target.table("services", cellspacing=0, align="center")

        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(" ", "&nbsp;")

        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]")
Пример #22
0
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 = { 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
Пример #23
0
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()

    user = dbutils.User.fromName(db, req.user)

    cursor.execute(
        "SELECT review, uid, merge, confirmed, tail FROM reviewmergeconfirmations WHERE id=%s", (confirmation_id,)
    )

    review_id, user_id, merge_id, confirmed, tail_id = cursor.fetchone()

    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 (All)")
            target.button("confirmNone").text("Confirm (None)")
            target.button("cancel").text("Cancel")

    page.utils.generateHeader(body, db, user, renderButtons, extra_links=[("r/%d" % review.id, "Back to Review", True)])

    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("confirm", align="center")
    basic.col(width="10%")
    basic.col(width="60%")
    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 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
Пример #24
0
    def render(target):
        table = target.table("services", cellspacing=0, align="center")

        headings = table.tr("headings")
        headings.th("name").text("Name")
        headings.th("path").text("Path")
        headings.th("pid").text("PID")
        headings.th("rss").text("RSS")
        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(" ", "&nbsp;")

        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 getRSS(pid):
            try:
                for line in open("/proc/%d/status" % pid):
                    words = line.split()
                    if words[0] == "VmRSS:":
                        if words[2].lower() == "kb": unit = 1024
                        elif words[2].lower() == "mb": unit = 1024 ** 2
                        else: raise Exception, "unknown unit: %s" % words[2]
                        return formatRSS(int(words[1]) * unit)
            except: pass
            return "N/A"

        for service_name, service_data in sorted(result["services"].items()):
            row = table.tr("service")
            row.td("name").text(service_name)
            row.td("path").text(service_data["path"])
            row.td("pid").text(service_data["pid"] if service_data["pid"] != -1 else "(not running)")
            row.td("rss").text(getRSS(service_data["pid"]))
            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

            row = table.tr("service")
            row.td("name").text("wsgi:%d" % index)
            row.td("path").text()
            row.td("pid").text(pid)
            row.td("rss").text(getRSS(int(pid)))
            row.td("uptime").innerHTML(formatUptime(uptime))

            commands = row.td("commands")
            commands.a(href="javascript:void(restartService('wsgi'));").text("[restart]")
Пример #25
0
def renderFile(db, target, user, review, file, first_file=False, options={}, conflicts=False, add_resources=True):
    if add_resources:
        addResources(db, user, review, options.get("compact", False), options.get("tabify"), target)

    if options.get("count_chunks"):
        deleted = 0
        inserted = 0
        if file.wasRemoved():
            file.loadOldLines(False)
            deleted = file.oldCount()
        else:
            for macro_chunk in file.macro_chunks:
                for chunk in macro_chunk.chunks:
                    deleted += chunk.delete_count
                    inserted += chunk.insert_count
        chunksText = "-%d/+%d lines" % (deleted, inserted)
    else:
        chunksText = ""

    compact = options.get("compact", False)

    file_table_class = "file sourcefont"
    compact = options.get("compact", False)

    if options.get("show"):
        file_table_class += " show"
    if options.get("expand"):
        file_table_class += " expanded"
        compact = False

    if first_file:
        file_table_class += " first"

    file_id = "f%d" % file.id
    customFileId = options.get("file_id")
    if customFileId:
        file_id = customFileId(file_id)

    target.script(type="text/javascript").text("calculateTabWidth();")

    table = target.table(file_table_class, width='100%', cellspacing=0, cellpadding=0, id=file_id, critic_file_id=file.id, critic_parent_index=options.get("parent_index"))

    if not compact:
        columns = table.colgroup()
        columns.col('edge').empty()
        columns.col('linenr').empty()
        columns.col('line').empty()
        columns.col('middle').empty()
        columns.col('middle').empty()
        columns.col('line').empty()
        columns.col('linenr').empty()
        columns.col('edge').empty()

    row = table.thead().tr()

    header_left = options.get("header_left")
    header_right = options.get("header_right")

    if header_left:
        header_left(db, row.td('left', colspan=4, align='left'), file)
    else:
        cell = row.td('left', colspan=4, align='left')

        commit = options.get("commit")
        if commit:
            review_arg = "&review=%d" % review.id if review else ""
            repository_arg = "&repository=%d" % file.repository.id if not review else ""

            cell.a("showtree root", href="showtree?sha1=%s&path=/%s%s" % (commit.sha1, review_arg, repository_arg)).text("root")
            cell.span("slash").text('/')

            components = file.path.split("/")
            for index, component in enumerate(components[:-1]):
                cell.a("showtree", href="showtree?sha1=%s&path=%s%s%s" % (commit.sha1, "/".join(components[:index + 1]), review_arg, repository_arg)).text(component)
                cell.span("slash").text('/')

            cell.a("showtree", href="showfile?sha1=%s&path=%s%s%s" % (commit.sha1, "/".join(components), review_arg, repository_arg)).text(components[-1])
        else:
            cell.text(file.path)

        if not compact:
            cell.comment("sha1: %s to %s" % (file.old_sha1, file.new_sha1))

    if header_right:
        header_right(db, row.td('right', colspan=4, align='right'), file)
    else:
        row.td('right', colspan=4, align='right').text(chunksText)

    next_old_offset = 1
    next_new_offset = 1

    display_type = options.get("display_type", "both")
    deleted_file = False
    added_file = False

    if not file.isBinaryChanges():
        if file.old_sha1 == 40 * '0':
            display_type = "new"

            if file.getLanguage() is None:
                limit = configuration.limits.MAXIMUM_ADDED_LINES_UNRECOGNIZED
            else:
                limit = configuration.limits.MAXIMUM_ADDED_LINES_RECOGNIZED

            count = file.newCount()
            if count > limit and len(file.macro_chunks) == 1 and len(file.macro_chunks[0].lines) == count:
                added_file = True
        elif file.new_sha1 == 40 * '0':
            display_type = "old"
            deleted_file = not options.get("include_deleted", False)

    def baseLineId(file, line, index):
        if line.type == diff.Line.DELETED:
            return "f%do%dn0" % (file.id, line.old_offset)
        elif line.type == diff.Line.INSERTED:
            return "f%do0n%d" % (file.id, line.new_offset)
        else:
            return "f%do%dn%d" % (file.id, line.old_offset, line.new_offset)

    def baseLineCellId(file, version, line):
        if line: return "f%d%s%d" % (file.id, version, line)
        else: return None

    customLineId = options.get("line_id")
    if customLineId:
        lineId = lambda file, line, index: customLineId(baseLineId(file, line, index))
    else:
        lineId = baseLineId

    customLineCellId = options.get("line_cell_id")
    if customLineCellId:
        lineCellId = lambda file, version, line: customLineCellId(baseLineCellId(file, version, line))
    else:
        lineCellId = baseLineCellId

    def lineType(line, index):
        type = line.type
        if type == diff.Line.DELETED: return "deleted"
        elif type == diff.Line.INSERTED: return "inserted"
        elif type == diff.Line.MODIFIED: return "modified whitespace" if line.is_whitespace else "modified"
        elif type == diff.Line.REPLACED: return "replaced"
        else: return "context"

    support_expand = options.get("support_expand", True)
    style = options.get("style", "horizontal")
    collapse_simple_hunks = user.getPreference(db, 'commit.diff.collapseSimpleHunks')

    content_before = options.get("content_before")
    if content_before:
        content = table.tbody('content')

        row = content.tr('content')
        row.td(colspan=2).text()
        content_before(db, row.td(colspan=4))
        row.td(colspan=2).text()

    if added_file or deleted_file:
        table.tbody('spacer').tr('spacer').td(colspan=8).text()

        verb = "added" if added_file else "deleted"
        side = "new" if added_file else "old"

        if added_file: count = file.newCount()
        else: count = file.oldCount()

        tbody = table.tbody('deleted')

        row = tbody.tr('deleted')
        row.td(colspan=2).text()
        row.td(colspan=4).h2().text("File was %s." % verb)
        row.td(colspan=2).text()

        row = tbody.tr('deleted')
        row.td(colspan=2).text()
        row.td(colspan=4).button(onclick="fetchFile(%d, '%s', event.currentTarget.parentNode.parentNode.parentNode);" % (file.id, side)).text("Fetch %d %s Lines" % (count, verb.capitalize()))
        row.td(colspan=2).text()

        table.tbody('spacer').tr('spacer').td(colspan=8).text()
    elif file.isBinaryChanges():
        table.tbody('spacer').tr('spacer').td(colspan=8).text()

        binary = table.tbody('binary')

        if file.wasAdded():
            title = "Binary file added."
        elif file.wasRemoved():
            title = "Binary file removed."
        else:
            title = "Binary file modified."

        row = binary.tr('binary')
        row.td(colspan=2).text()
        row.td(colspan=4).h2().text(title)
        row.td(colspan=2).text()

        row = binary.tr('download')
        row.td(colspan=2).text()
        cell = row.td(colspan=4)

        def linkToFile(target, file, sha1):
            is_image = False

            try:
                base, extension = file.path.rsplit(".")
                if configuration.mimetypes.MIMETYPES.get(extension, "").startswith("image/"):
                    is_image = True
            except:
                pass

            url = "download/%s?sha1=%s&repository=%d" % (file.path, sha1, file.repository.id)
            link = target.a(href=url)

            if is_image: link.img(src=url)
            else: link.text(sha1)

        if file.wasAdded():
            linkToFile(cell, file, file.new_sha1)
        elif file.wasRemoved():
            linkToFile(cell, file, file.old_sha1)
        else:
            linkToFile(cell, file, file.old_sha1)
            cell.innerHTML(" &#8594; ")
            linkToFile(cell, file, file.new_sha1)

        row.td(colspan=2).text()

        table.tbody('spacer').tr('spacer').td(colspan=8).text()
    else:
        if options.get("tabify"):
            tabwidth = file.getTabWidth()
            indenttabsmode = file.getIndentTabsMode()
            tabify = lambda line: htmlutils.tabify(line, tabwidth, indenttabsmode)
        else:
            tabify = lambda line: line

        code_contexts = CodeContexts(db, file.new_sha1,
                                     file.macro_chunks[0].lines[0].new_offset,
                                     file.macro_chunks[-1].lines[-1].new_offset)

        blocks = [("[%d,%d]" % (macro_chunk.lines[0].new_offset, macro_chunk.lines[-1].new_offset))
                  for macro_chunk in file.macro_chunks]

        target.script(type="text/javascript").text("blocks[%d] = [%s];" % (file.id, ",".join(blocks)))

        for index, macro_chunk in enumerate(file.macro_chunks):
            first_line = macro_chunk.lines[0]
            last_line = macro_chunk.lines[-1]

            spacer = table.tbody('spacer')

            if support_expand and next_old_offset < first_line.old_offset and next_new_offset < first_line.new_offset:
                row = spacer.tr('expand').td(colspan='8')
                expandHTML(db, file, next_old_offset, next_new_offset, first_line.old_offset - next_old_offset, row)

            code_context = code_contexts.find(first_line.new_offset)
            if code_context: spacer.tr('context').td(colspan='8').text(code_context)

            spacer.tr('spacer').td(colspan='8').text()

            lines = table.tbody('lines')

            local_display_type = display_type

            for line in macro_chunk.lines:
                if line.type != diff.Line.INSERTED:
                    match = re_tailws.match(line.old_value)
                    if match:
                        line.old_value = match.group(1) + "<i class='tailws'>" + match.group(2) + "</i>" + match.group(3)
                if line.type != diff.Line.DELETED:
                    match = re_tailws.match(line.new_value)
                    if match:
                        line.new_value = match.group(1) + "<i class='tailws'>" + match.group(2) + "</i>" + match.group(3)

                if line.old_value:
                    line.old_value = line.old_value.replace("\r", "<i class='cr'></i>")
                if line.new_value:
                    line.new_value = line.new_value.replace("\r", "<i class='cr'></i>")

            if collapse_simple_hunks:
                if local_display_type == "both":
                    deleted = False
                    inserted = False

                    for line in macro_chunk.lines:
                        if line.type == diff.Line.MODIFIED or line.type == diff.Line.REPLACED:
                            break
                        elif line.type == diff.Line.DELETED:
                            if inserted: break
                            deleted = True
                        elif line.type == diff.Line.INSERTED:
                            if deleted: break
                            inserted = True
                    else:
                        if deleted: local_display_type = "old"
                        if inserted: local_display_type = "new"

            if compact:
                def packSyntaxHighlighting(line):
                    return re_tag.sub(lambda m: "<%s%s>" % (m.group(1), m.group(2)), line)

                items = []
                for line in macro_chunk.lines:
                    if line.type == diff.Line.MODIFIED and line.is_whitespace:
                        line_type = diff.Line.WHITESPACE
                    elif conflicts and line.type == diff.Line.DELETED and line.isConflictMarker():
                        line_type = diff.Line.CONFLICT
                    else:
                        line_type = line.type
                    data = [str(line_type)]
                    if line.type != diff.Line.INSERTED:
                        data.append(jsify(packSyntaxHighlighting(tabify(line.old_value)), json=True))
                    if line.type != diff.Line.DELETED:
                        data.append(jsify(packSyntaxHighlighting(tabify(line.new_value)), json=True))
                    items.append("[%s]" % ",".join(data))
                data = "[%d,%d,%d,%d,%s]" % (file.id,
                                             2 if local_display_type == "both" else 1,
                                             macro_chunk.lines[0].old_offset,
                                             macro_chunk.lines[0].new_offset,
                                             "[%s]" % ",".join(items))
                lines.comment(data.replace("--", "-\u002d"))
            elif style == "vertical" or local_display_type != "both":
                linesIterator = iter(macro_chunk.lines)
                line = linesIterator.next()

                def lineHTML(what, file, line, is_whitespace, target):
                    line_class = what

                    if is_whitespace and line.type == diff.Line.MODIFIED:
                        line_class = "modified"

                    if what == "deleted":
                        linenr = line.old_offset
                    else:
                        linenr = line.new_offset

                    row = target.tr("line " + line_class, id=lineId(file, line, 0))
                    row.td("edge").text()
                    row.td("linenr old").text(linenr)

                    if what == "deleted" or local_display_type == "old":
                        code = line.old_value
                        lineClass = "old"
                    else:
                        code = line.new_value
                        lineClass = "new"

                    if not code: code = "&nbsp;"

                    row.td('line single ' + lineClass, colspan=4, id=lineCellId(file, lineClass[0], linenr)).innerHTML(tabify(code))
                    row.td('linenr new').text(linenr)

                    row.td("edge").text()

                try:
                    while line:
                        while line.type == diff.Line.CONTEXT:
                            lineHTML("context", file, line, False, lines)
                            line = linesIterator.next()

                        deleted = []
                        inserted = []

                        while line.is_whitespace:
                            lineHTML("modified", file, line, True, lines)
                            line = linesIterator.next()

                        previous_type = diff.Line.DELETED

                        try:
                            while line.type >= previous_type and not line.is_whitespace:
                                if line.type != diff.Line.INSERTED: deleted.append(line)
                                if line.type != diff.Line.DELETED: inserted.append(line)
                                previous_type = line.type
                                line = None
                                line = linesIterator.next()
                        except StopIteration:
                            line = None

                        for deletedLine in deleted:
                            lineHTML("deleted", file, deletedLine, False, lines)
                        for insertedLine in inserted:
                            lineHTML("inserted", file, insertedLine, False, lines)
                except StopIteration:
                    pass
            elif style == "horizontal":
                for line in macro_chunk.lines:
                    old_offset = None
                    new_offset = None
                    old_line = None
                    new_line = None

                    if line.type != diff.Line.INSERTED:
                        old_offset = line.old_offset
                        old_line = tabify(line.old_value)

                    if line.type != diff.Line.DELETED:
                        new_offset = line.new_offset
                        new_line = tabify(line.new_value)

                    if not old_line: old_line = "&nbsp;"
                    if old_line is None: old_offset = None
                    if not new_line: new_line = "&nbsp;"
                    if new_line is None: new_offset = None

                    line_type = lineType(line, 0)

                    if conflicts and line.isConflictMarker():
                        line_type += " conflict"

                    row = ("<tr class='line %s' id='%s'>"
                             "<td class='edge'>&nbsp;</td>"
                             "<td class='linenr old'>%s</td>"
                             "<td class='line old'%s>%s</td>"
                             "<td class='middle' colspan=2>&nbsp;</td>"
                             "<td class='line new'%s>%s</td>"
                             "<td class='linenr new'>%s</td>"
                             "<td class='edge'>&nbsp;</td>"
                           "</tr>\n") % (line_type, lineId(file, line, 0),
                                         str(old_offset) if old_offset else "&nbsp;",
                                         " id='%s'" % lineCellId(file, "o", old_offset) if old_offset else "", old_line,
                                         " id='%s'" % lineCellId(file, "n", new_offset) if new_offset else "", new_line,
                                         str(new_offset) if new_offset else "&nbsp;")

                    lines.innerHTML(row)

            next_old_offset = last_line.old_offset + 1
            next_new_offset = last_line.new_offset + 1

        spacer = table.tbody('spacer')

        if support_expand and next_old_offset < file.oldCount() + 1 and next_new_offset < file.newCount() + 1:
            row = spacer.tr('expand').td(colspan='8')
            expandHTML(db, file, next_old_offset, next_new_offset, 1 + file.oldCount() - next_old_offset, row)

        spacer.tr('spacer').td(colspan='8').text()

    content_after = options.get("content_after")
    if content_after:
        content = table.tbody('content')

        row = content.tr('content')
        row.td(colspan=2).text()
        content_after(db, row.td(colspan=4), file=file)
        row.td(colspan=2).text()

        content.tr('spacer').td(colspan=8).text()

    row = table.tfoot().tr()
    cell = row.td('left', colspan=4)

    commit = options.get("commit")
    if commit:
        review_arg = "&review=%d" % review.id if review else ""
        repository_arg = "&repository=%d" % file.repository.id if not review else ""

        cell.a("showtree root", href="showtree?sha1=%s&path=/%s%s" % (commit.sha1, review_arg, repository_arg)).text("root")
        cell.span("slash").text('/')

        components = file.path.split("/")
        for index, component in enumerate(components[:-1]):
            cell.a("showtree", href="showtree?sha1=%s&path=%s%s%s" % (commit.sha1, "/".join(components[:index + 1]), review_arg, repository_arg)).text(component)
            cell.span("slash").text('/')

        cell.a("showtree", href="showfile?sha1=%s&path=%s%s%s" % (commit.sha1, "/".join(components), review_arg, repository_arg)).text(components[-1])
    else:
        cell.text(file.path)

    row.td('right', colspan=4).text(chunksText)
Пример #26
0
def renderRepositories(req, db, user):
    req.content_type = "text/html; charset=utf-8"

    document = htmlutils.Document(req)
    document.setTitle("Repositories")

    html = document.html()
    head = html.head()
    body = html.body()

    def generateRight(target):
        if user.hasRole(db, "repositories"):
            target.a("button", href="newrepository").text("Add Repository")

    page.utils.generateHeader(body, db, user, current_page="repositories", generate_right=generateRight)

    document.addExternalStylesheet("resource/repositories.css")
    document.addExternalScript("resource/repositories.js")
    document.addInternalScript(user.getJS())

    if user.name == req.user and user.hasRole(db, "administrator"):
        document.addInternalScript("user.administrator = true;")

    cursor = db.cursor()
    cursor.execute("SELECT id, name, path, parent, branch FROM repositories ORDER BY name ASC")

    rows = cursor.fetchall()

    class Repository:
        def __init__(self, repository_id, name, path, parent_id, branch_id):
            self.id = repository_id
            self.name = name
            self.path = path
            self.parent_id = parent_id
            self.branch_id = branch_id
            self.default_remote = None
            self.location = gitutils.Repository.constructURL(db, user, path)

    repositories = list(Repository(*row) for row in rows)
    repository_by_id = dict((repository.id, repository) for repository in repositories)

    def render(target):
        table = target.table("repositories callout")

        headings = table.tr("headings")
        headings.th("name").text("Short name")
        headings.th("location").text("Location")
        headings.th("upstream").text("Upstream")

        table.tr("spacer").td("spacer", colspan=3)

        for repository in repositories:
            row = table.tr("repository %s" % repository.name)
            row.td("name").text(repository.name)
            row.td("location").text(repository.location)

            if repository.parent_id:
                row.td("upstream").text(repository_by_id[repository.parent_id].name)
            else:
                row.td("upstream").text()

            cursor.execute("""SELECT id, local_name, remote, remote_name, disabled
                                FROM trackedbranches
                               WHERE repository=%s
                            ORDER BY id ASC""",
                           (repository.id,))

            details = table.tr("details %s" % repository.name).td(colspan=3)

            branches = [(branch_id, local_name, remote, remote_name, disabled)
                        for branch_id, local_name, remote, remote_name, disabled in cursor
                        if not local_name.startswith("r/")]

            if branches:
                trackedbranches = details.table("trackedbranches", cellspacing=0)
                trackedbranches.tr().th("title", colspan=5).text("Tracked Branches")

                row = trackedbranches.tr("headings")
                row.th("localname").text("Local branch")
                row.th("remote").text("Repository")
                row.th("remotename").text("Remote branch")
                row.th("enabled").text("Enabled")
                row.th("users").text("Users")

                default_remote = ""

                for branch_id, local_name, remote, remote_name, disabled in sorted(branches, key=lambda branch: branch[1]):
                    cursor.execute("SELECT uid FROM trackedbranchusers WHERE branch=%s", (branch_id,))

                    user_ids = [user_id for (user_id,) in cursor.fetchall()]

                    row = trackedbranches.tr("branch", critic_branch_id=branch_id, critic_user_ids=",".join(map(str, user_ids)))

                    if local_name == "*":
                        row.td("localname").i().text("Tags")
                        default_remote = remote
                    else:
                        row.td("localname").text(local_name)
                        if local_name == "master" and not default_remote:
                            default_remote = remote
                    row.td("remote").text(remote)
                    if remote_name == "*":
                        row.td("remotename").i().text("N/A")
                    else:
                        row.td("remotename").text(remote_name)
                    row.td("enabled").text("No" if disabled else "Yes")

                    cell = row.td("users")

                    for index, user_id in enumerate(user_ids):
                        if index: cell.text(", ")
                        trackedbranch_user = dbutils.User.fromId(db, user_id)
                        cell.span("user").text(trackedbranch_user.name)

                if default_remote:
                    repository.default_remote = default_remote

            buttons = details.div("buttons")
            buttons.button(onclick="addTrackedBranch(%d);" % repository.id).text("Add Tracked Branch")

    paleyellow = page.utils.PaleYellowTable(body, "Repositories")
    paleyellow.addCentered(render)

    repositories_js = []

    for repository in repositories:
        name = htmlutils.jsify(repository.name)
        path = htmlutils.jsify(repository.path)
        location = htmlutils.jsify(repository.location)
        default_remote = htmlutils.jsify(repository.default_remote)

        repositories_js.append(("%d: { name: %s, path: %s, location: %s, defaultRemoteLocation: %s }"
                                % (repository.id, name, path, location, default_remote)))

    document.addInternalScript("var repositories = { %s };" % ", ".join(repositories_js))

    return document
Пример #27
0
    def render(target):
        table = target.table("services", cellspacing=0, align="center")

        headings = table.tr("headings")
        headings.th("name").text("Name")
        headings.th("path").text("Path")
        headings.th("pid").text("PID")
        headings.th("rss").text("RSS")
        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(" ", "&nbsp;")

        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 getRSS(pid):
            try:
                for line in open("/proc/%d/status" % pid):
                    words = line.split()
                    if words[0] == "VmRSS:":
                        if words[2].lower() == "kb": unit = 1024
                        elif words[2].lower() == "mb": unit = 1024**2
                        else: raise Exception, "unknown unit: %s" % words[2]
                        return formatRSS(int(words[1]) * unit)
            except:
                pass
            return "N/A"

        for service_name, service_data in sorted(result["services"].items()):
            row = table.tr("service")
            row.td("name").text(service_name)
            row.td("path").text(service_data["path"])
            row.td("pid").text(service_data["pid"] if service_data["pid"] != -1
                               else "(not running)")
            row.td("rss").text(getRSS(service_data["pid"]))
            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

            row = table.tr("service")
            row.td("name").text("wsgi:%d" % index)
            row.td("path").text()
            row.td("pid").text(pid)
            row.td("rss").text(getRSS(int(pid)))
            row.td("uptime").innerHTML(formatUptime(uptime))

            commands = row.td("commands")
            commands.a(href="javascript:void(restartService('wsgi'));").text(
                "[restart]")
Пример #28
0
def execute(db, req, user):
    cursor = db.cursor()

    installs = Extension.getInstalls(db, user)

    argv = None
    stdin_data = None

    for extension_id, version_id, version_sha1, is_universal in installs:
        handlers = []

        if version_id is not None:
            cursor.execute(
                """SELECT script, function, path
                                FROM extensionroles
                                JOIN extensionpageroles ON (role=id)
                               WHERE version=%s
                            ORDER BY id ASC""", (version_id, ))

            for script, function, path_regexp in cursor:
                if re.match(path_regexp, req.path):
                    handlers.append((script, function))

            if not handlers:
                continue

            extension_path = getExtensionInstallPath(version_sha1)
            manifest = Manifest.load(extension_path)
        else:
            try:
                extension = Extension.fromId(db, extension_id)
            except ExtensionError:
                # If the author/hosting user no longer exists, or the extension
                # directory no longer exists or is inaccessible, ignore the
                # extension.
                continue

            try:
                manifest = Manifest.load(extension.getPath())
            except ManifestError:
                # If the MANIFEST is missing or invalid, we can't know whether
                # the extension has a page role handling the path, so assume it
                # doesn't and ignore it.
                continue

            for role in manifest.roles:
                if isinstance(role, PageRole) and re.match(
                        role.regexp, req.path):
                    handlers.append((role.script, role.function))

            if not handlers:
                continue

        if argv is None:

            def param(raw):
                parts = raw.split("=", 1)
                if len(parts) == 1:
                    return "%s: null" % jsify(decodeURIComponent(raw))
                else:
                    return "%s: %s" % (jsify(decodeURIComponent(
                        parts[0])), jsify(decodeURIComponent(parts[1])))

            if req.query:
                query = (
                    "Object.freeze({ raw: %s, params: Object.freeze({ %s }) })"
                    % (jsify(req.query), ", ".join(
                        map(param, req.query.split("&")))))
            else:
                query = "null"

            headers = ("Object.freeze({ %s })" % ", ".join(
                ("%s: %s" % (jsify(name), jsify(value)))
                for name, value in req.getRequestHeaders().items()))

            argv = ("[%(method)s, %(path)s, %(query)s, %(headers)s]" % {
                'method': jsify(req.method),
                'path': jsify(req.path),
                'query': query,
                'headers': headers
            })

        if req.method == "POST":
            if stdin_data is None:
                stdin_data = req.read()

        for script, function in handlers:
            before = time.time()

            try:
                stdout_data = executeProcess(
                    manifest,
                    "page",
                    script,
                    function,
                    extension_id,
                    user.id,
                    argv,
                    configuration.extensions.LONG_TIMEOUT,
                    stdin=stdin_data)
            except ProcessTimeout:
                req.setStatus(500, "Extension Timeout")
                return "Extension timed out!"
            except ProcessError as error:
                req.setStatus(500, "Extension Failure")
                if error.returncode < 0:
                    return ("Extension failure: terminated by signal %d\n" %
                            -error.returncode)
                else:
                    return ("Extension failure: returned %d\n%s" %
                            (error.returncode, error.stderr))

            after = time.time()

            status = None
            headers = {}

            if not stdout_data:
                return False

            while True:
                try:
                    line, stdout_data = stdout_data.split("\n", 1)
                except:
                    req.setStatus(500, "Extension Error")
                    return "Extension error: output format error.\n%r\n" % stdout_data

                if status is None:
                    try:
                        status = int(line.strip())
                    except:
                        req.setStatus(500, "Extension Error")
                        return "Extension error: first line should contain only a numeric HTTP status code.\n%r\n" % line
                elif not line:
                    break
                else:
                    try:
                        name, value = line.split(":", 1)
                    except:
                        req.setStatus(500, "Extension Error")
                        return "Extension error: header line should be on 'name: value' format.\n%r\n" % line
                    headers[name.strip()] = value.strip()

            if status is None:
                req.setStatus(500, "Extension Error")
                return "Extension error: first line should contain only a numeric HTTP status code.\n"

            content_type = "text/plain"

            for name, value in headers.items():
                if name.lower() == "content-type":
                    content_type = value
                    del headers[name]
                else:
                    headers[name] = value

            req.setStatus(status)
            req.setContentType(content_type)

            for name, value in headers.items():
                req.addResponseHeader(name, value)

            if content_type == "text/tutorial":
                req.setContentType("text/html")
                return renderTutorial(db, user, stdout_data)

            if content_type.startswith("text/html"):
                stdout_data += "\n\n<!-- extension execution time: %.2f seconds -->\n" % (
                    after - before)

            return stdout_data

    return False
Пример #29
0
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))

    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()

    page.utils.generateHeader(body, db, user, 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 user.name != req.user and req.getUser(db).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.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, status_id=None):
        main_row = basic.tr('line')
        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")
            target.button(onclick="saveFullname();").text("Save")
            target.button(onclick="resetFullname();").text("Reset")

    def renderEmail(target):
        if readonly: target.text(user.email)
        else:
            target.input("value", id="user_email", value=user.email)
            target.span("status", id="status_email")
            target.button(onclick="saveEmail();").text("Save")
            target.button(onclick="resetEmail();").text("Reset")

    def renderGitEmails(target):
        if readonly: target.text(gitemails)
        else:
            target.input("value", id="user_gitemails", value=gitemails)
            target.span("status", id="status_gitemails")
            target.button(onclick="saveGitEmails();").text("Save")
            target.button(onclick="resetGitEmails();").text("Reset")

    def renderPassword(target):
        target.text("****")
        if not readonly:
            target.button(onclick="changePassword();").text("Change")

    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.", status_id="status_fullname")
    row("Email", renderEmail, "This is the primary email address, to which emails are sent.", status_id="status_email")
    row("Git Emails", renderGitEmails, "These email addresses are used to map Git commits to the user.", status_id="status_gitemails")

    if configuration.base.AUTHENTICATION_MODE == "critic":
        row("Password", renderPassword)

    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, filters.delegate
                        FROM repositories
                        JOIN filters ON (filters.repository=repositories.id)
                       WHERE filters.uid=%s
                    ORDER BY repositories.name, filters.type, filters.path""",
                   (user.id,))

    rows = cursor.fetchall()

    if rows:
        repository = None
        repository_filters = None
        tbody_reviewer = None
        tbody_watcher = None
        tbody_ignored = None

        count_matched_files = {}

        for (repository_id, repository_name, repository_path,
             filter_id, filter_type, filter_path, filter_delegates) 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 = None

            if filter_type == "reviewer":
                if not tbody_reviewer:
                    tbody_reviewer = repository_filters.tbody()
                    tbody_reviewer.tr().th(colspan=4).text("Reviewer")
                tbody = tbody_reviewer
            elif filter_type == "watcher":
                if not tbody_watcher:
                    tbody_watcher = repository_filters.tbody()
                    tbody_watcher.tr().th(colspan=4).text("Watcher")
                tbody = tbody_watcher
            else:
                if not tbody_ignored:
                    tbody_ignored = repository_filters.tbody()
                    tbody_ignored.tr().th(colspan=4).text("Ignored")
                tbody = tbody_ignored

            row = tbody.tr()
            row.td("path").text(filter_path)

            delegates = row.td("delegates")
            if filter_delegates:
                delegates.i().text("Delegates: ")
                delegates.span("names").text(", ".join(filter_delegates.split(",")))

            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_delegates))
            links.a(href="javascript:void(editFilter(%s, %d, %s, %s, %s));" % arguments).text("[edit]")
            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]")

        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")

    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")

        paragraph = dialog.p()
        paragraph.b().text("Path:")
        paragraph.br()
        paragraph.input(name="path", type="text")
        paragraph.span("matchedfiles")

        paragraph = dialog.p()
        paragraph.b().text("Delegates:")
        paragraph.br()
        paragraph.input(name="delegates", type="text")

        paragraph = dialog.p()
        label = paragraph.label()
        label.input(name="apply", type="checkbox", checked="checked")
        label.b().text("Apply to existing reviews")

    profiler.output(db, user, document)

    return document
Пример #30
0
 def getJSConstructor(self):
     from htmlutils import jsify
     if self.base:
         return "new Branch(%d, %s, %s)" % (self.id, jsify(self.name), self.base.getJSConstructor())
     else:
         return "new Branch(%d, %s, null)" % (self.id, jsify(self.name))
Пример #31
0
def processFilterHookEvent(db, event_id, logfn):
    cursor = db.cursor()

    cursor.execute(
        """SELECT filters.extension, filters.uid, filters.path,
                             filters.name, events.review, events.uid, events.data
                        FROM extensionfilterhookevents AS events
                        JOIN extensionhookfilters AS filters ON (filters.id=events.filter)
                       WHERE events.id=%s""", (event_id, ))

    # Note:
    # - filter_user_id / filter_user represent the user whose filter was
    #   triggered.
    # - user_id /user represent the user that added commits and thereby
    #   triggered the filter.

    (extension_id, filter_user_id, filter_path, filterhook_name, review_id,
     user_id, filter_data) = cursor.fetchone()

    extension = Extension.fromId(db, extension_id)
    filter_user = dbutils.User.fromId(db, filter_user_id)

    installed_sha1, _ = extension.getInstalledVersion(db, filter_user)

    if installed_sha1 is False:
        # Invalid event (user doesn't have extension installed); do nothing.
        # The event will be deleted by the caller.
        return

    manifest = extension.getManifest(sha1=installed_sha1)

    for role in manifest.roles:
        if isinstance(role, FilterHookRole) and role.name == filterhook_name:
            break
    else:
        # Invalid event (installed version of extension doesn't have the named
        # filter hook role); do nothing.  The event will be deleted by the
        # caller.
        return

    cursor.execute(
        """SELECT commit
                        FROM extensionfilterhookcommits
                       WHERE event=%s""", (event_id, ))
    commit_ids = [commit_id for (commit_id, ) in cursor]

    cursor.execute(
        """SELECT file
                        FROM extensionfilterhookfiles
                       WHERE event=%s""", (event_id, ))
    file_ids = [file_id for (file_id, ) in cursor]

    argv = """

(function () {
   var review = new critic.Review(%(review_id)d);
   var user = new critic.User(%(user_id)d);
   var repository = review.repository;
   var commits = new critic.CommitSet(
     %(commit_ids)r.map(
       function (commit_id) {
         return repository.getCommit(commit_id);
       }));
   var files = %(file_ids)r.map(
     function (file_id) {
       return critic.File.find(file_id);
     });
   return [%(filter_data)s, review, user, commits, files];
 })()

""" % {
        "filter_data": htmlutils.jsify(filter_data),
        "review_id": review_id,
        "user_id": user_id,
        "commit_ids": commit_ids,
        "file_ids": file_ids
    }

    argv = re.sub("[ \n]+", " ", argv.strip())

    logfn("argv=%r" % argv)
    logfn("script=%r" % role.script)
    logfn("function=%r" % role.function)

    try:
        executeProcess(manifest, "filterhook", role.script, role.function,
                       extension_id, filter_user_id, argv,
                       configuration.extensions.LONG_TIMEOUT)
    except (ProcessTimeout, ProcessError) as error:
        review = dbutils.Review.fromId(db, review_id)

        recipients = set([filter_user])

        author = extension.getAuthor(db)
        if author is None:
            recipients.update(dbutils.User.withRole(db, "administrator"))
        else:
            recipients.add(author)

        body = """\
An error occurred while processing an extension hook filter event!

Filter details:

  Extension:   %(extension.title)s
  Filter hook: %(role.title)s
  Repository:  %(repository.name)s
  Path:        %(filter.path)s
  Data:        %(filter.data)s

Event details:

  Review:  r/%(review.id)d "%(review.summary)s"
  Commits: %(commits)s

Error details:

  Error:  %(error.message)s
  Output:%(error.output)s

-- critic"""

        commits = (gitutils.Commit.fromId(db, review.repository, commit_id)
                   for commit_id in commit_ids)
        commits_text = "\n           ".join(
            ('%s "%s"' % (commit.sha1[:8], commit.niceSummary())
             for commit in commits))

        if isinstance(error, ProcessTimeout):
            error_output = " N/A"
        else:
            error_output = "\n\n    " + "\n    ".join(
                error.stderr.splitlines())

        body = body % {
            "extension.title": extension.getTitle(db),
            "role.title": role.title,
            "repository.name": review.repository.name,
            "filter.path": filter_path,
            "filter.data": htmlutils.jsify(filter_data),
            "review.id": review.id,
            "review.summary": review.summary,
            "commits": commits_text,
            "error.message": error.message,
            "error.output": error_output
        }

        mailutils.sendMessage(recipients=list(recipients),
                              subject="Failed: " + role.title,
                              body=body)
Пример #32
0
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
Пример #33
0
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
Пример #34
0
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 (comma-separated) 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
Пример #35
0
def execute(db, req, user):
    cursor = db.cursor()

    installs = Extension.getInstalls(db, user)

    argv = None
    stdin_data = None

    for extension_id, version_id, version_sha1, is_universal in installs:
        handlers = []

        if version_id is not None:
            cursor.execute("""SELECT script, function, path
                                FROM extensionroles
                                JOIN extensionpageroles ON (role=id)
                               WHERE version=%s
                            ORDER BY id ASC""",
                           (version_id,))

            for script, function, path_regexp in cursor:
                if re.match(path_regexp, req.path):
                    handlers.append((script, function))

            if not handlers:
                continue

            extension_path = getExtensionInstallPath(version_sha1)
            manifest = Manifest.load(extension_path)
        else:
            try:
                extension = Extension.fromId(db, extension_id)
            except ExtensionError:
                # If the author/hosting user no longer exists, or the extension
                # directory no longer exists or is inaccessible, ignore the
                # extension.
                continue

            try:
                manifest = Manifest.load(extension.getPath())
            except ManifestError:
                # If the MANIFEST is missing or invalid, we can't know whether
                # the extension has a page role handling the path, so assume it
                # doesn't and ignore it.
                continue

            for role in manifest.roles:
                if isinstance(role, PageRole) and re.match(role.regexp, req.path):
                    handlers.append((role.script, role.function))

            if not handlers:
                continue

        if argv is None:
            def param(raw):
                parts = raw.split("=", 1)
                if len(parts) == 1:
                    return "%s: null" % jsify(decodeURIComponent(raw))
                else:
                    return "%s: %s" % (jsify(decodeURIComponent(parts[0])),
                                       jsify(decodeURIComponent(parts[1])))

            if req.query:
                query = ("Object.freeze({ raw: %s, params: Object.freeze({ %s }) })"
                         % (jsify(req.query),
                            ", ".join(map(param, req.query.split("&")))))
            else:
                query = "null"

            headers = ("Object.freeze({ %s })"
                       % ", ".join(("%s: %s" % (jsify(name), jsify(value)))
                                   for name, value in req.getRequestHeaders().items()))

            argv = ("[%(method)s, %(path)s, %(query)s, %(headers)s]"
                    % { 'method': jsify(req.method),
                        'path': jsify(req.path),
                        'query': query,
                        'headers': headers })

        if req.method == "POST":
            if stdin_data is None:
                stdin_data = req.read()

        for script, function in handlers:
            before = time.time()

            try:
                stdout_data = executeProcess(
                    manifest, "page", script, function, extension_id, user.id,
                    argv, configuration.extensions.LONG_TIMEOUT, stdin=stdin_data)
            except ProcessTimeout:
                req.setStatus(500, "Extension Timeout")
                return "Extension timed out!"
            except ProcessError as error:
                req.setStatus(500, "Extension Failure")
                if error.returncode < 0:
                    return ("Extension failure: terminated by signal %d\n"
                            % -error.returncode)
                else:
                    return ("Extension failure: returned %d\n%s"
                            % (error.returncode, error.stderr))

            after = time.time()

            status = None
            headers = {}

            if not stdout_data:
                return False

            while True:
                try: line, stdout_data = stdout_data.split("\n", 1)
                except:
                    req.setStatus(500, "Extension Error")
                    return "Extension error: output format error.\n%r\n" % stdout_data

                if status is None:
                    try: status = int(line.strip())
                    except:
                        req.setStatus(500, "Extension Error")
                        return "Extension error: first line should contain only a numeric HTTP status code.\n%r\n" % line
                elif not line:
                    break
                else:
                    try: name, value = line.split(":", 1)
                    except:
                        req.setStatus(500, "Extension Error")
                        return "Extension error: header line should be on 'name: value' format.\n%r\n" % line
                    headers[name.strip()] = value.strip()

            if status is None:
                req.setStatus(500, "Extension Error")
                return "Extension error: first line should contain only a numeric HTTP status code.\n"

            content_type = "text/plain"

            for name, value in headers.items():
                if name.lower() == "content-type":
                    content_type = value
                    del headers[name]
                else:
                    headers[name] = value

            req.setStatus(status)
            req.setContentType(content_type)

            for name, value in headers.items():
                req.addResponseHeader(name, value)

            if content_type == "text/tutorial":
                req.setContentType("text/html")
                return renderTutorial(db, user, stdout_data)

            if content_type.startswith("text/html"):
                stdout_data += "\n\n<!-- extension execution time: %.2f seconds -->\n" % (after - before)

            return stdout_data

    return False
Пример #36
0
 def getJSConstructor(self):
     return "new Comment(%d, %s, %s, %s, %s)" % (
         self.id, self.user.getJSConstructor(),
         jsify(strftime("%Y-%m-%d %H:%M", self.time.timetuple())),
         jsify(self.state), jsify(self.comment))
Пример #37
0
 def getJS(self):
     return "var repository = critic.repository = new Repository(%d, %s, %s);" % (self.id, htmlutils.jsify(self.name), htmlutils.jsify(self.path))
Пример #38
0
 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")
Пример #39
0
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
Пример #40
0
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(" ", "&nbsp;"))
    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)) +
                                                        "&#8230;/" + 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)) + "&#8230;/" +
                htmlutils.htmlify(file_name))
            row.td().text()

    outputDirectory("", "", root_directories, root_files)

    return document
Пример #41
0
def renderCodeCommentChain(db,
                           target,
                           user,
                           review,
                           chain,
                           context_lines=3,
                           compact=False,
                           tabify=False,
                           original=False,
                           changeset=None,
                           linkify=False):
    repository = review.repository

    old_sha1 = None
    new_sha1 = None

    old = 1
    new = 2

    cursor = db.cursor()

    file_id = chain.file_id
    file_path = dbutils.describe_file(db, file_id)

    if (chain.state != "addressed"
            or original) and chain.first_commit == chain.last_commit:
        sha1 = chain.first_commit.getFileSHA1(file_path)

        cursor.execute(
            "SELECT first_line, last_line FROM commentchainlines WHERE chain=%s AND sha1=%s",
            (chain.id, sha1))
        first_line, last_line = cursor.fetchone()

        file = diff.File(file_id,
                         file_path,
                         sha1,
                         sha1,
                         review.repository,
                         chunks=[])
        file.loadNewLines(True)

        start = max(1, first_line - context_lines)
        end = min(file.newCount(), last_line + context_lines)
        count = end + 1 - start

        lines = file.newLines(True)
        lines = [
            diff.Line(diff.Line.CONTEXT, start + index,
                      lines[start + index - 1], start + index,
                      lines[start + index - 1]) for index in range(count)
        ]

        file.macro_chunks = [diff.MacroChunk([], lines)]

        use = new
        display_type = "new"
        commit_url_component = "sha1=%s" % chain.first_commit.sha1
    else:
        if chain.state == "addressed" and not original and review.containsCommit(
                db, chain.addressed_by):
            parent = gitutils.Commit.fromSHA1(db, review.repository,
                                              chain.addressed_by.parents[0])
            child = chain.addressed_by
            use = old
        else:
            parent = chain.first_commit
            child = chain.last_commit

            if parent == child:
                if chain.origin == "old":
                    cursor.execute(
                        """SELECT changesets.child
                                        FROM changesets, reviewchangesets
                                       WHERE changesets.parent=%s
                                         AND reviewchangesets.changeset=changesets.id
                                         AND reviewchangesets.review=%s""",
                        [child.getId(db), review.id])

                    try:
                        child = gitutils.Commit.fromId(db, repository,
                                                       cursor.fetchone()[0])
                    except:
                        parent = gitutils.Commit.fromSHA1(
                            db, repository, child.parents[0])
                else:
                    parent = gitutils.Commit.fromSHA1(db, repository,
                                                      child.parents[0])

            if chain.origin == "old": use = old
            else: use = new

        if parent.sha1 in child.parents and len(child.parents) == 1:
            commit = child
            from_commit = None
            to_commit = None
        else:
            commit = None
            from_commit = parent
            to_commit = child

        if changeset:
            assert ((changeset.parent == from_commit
                     and changeset.child == to_commit) if commit is None else
                    (changeset.parent.sha1 == commit.parents[0]
                     and changeset.child == commit))
            assert changeset.getFile(file_id)
        else:
            changeset = changeset_utils.createChangeset(
                db,
                user,
                repository,
                commit=commit,
                from_commit=from_commit,
                to_commit=to_commit,
                filtered_file_ids=set((file_id, )))[0]

        file = changeset.getFile(file_id)

        if not file:
            if chain.state == "addressed" and not original:
                renderCodeCommentChain(db,
                                       target,
                                       user,
                                       review,
                                       chain,
                                       context_lines,
                                       compact,
                                       tabify,
                                       original=True)
                return
            else:
                raise

        # Commit so that the diff and its analysis, written to the database by createChangeset(),
        # can be reused later.
        db.commit()

        old_sha1 = file.old_sha1
        new_sha1 = file.new_sha1

        if use == old and old_sha1 == '0' * 40: use = new
        elif use == new and new_sha1 == '0' * 40: use = old

        if use == old: sha1 = old_sha1
        else: sha1 = new_sha1

        cursor.execute(
            "SELECT first_line, last_line FROM commentchainlines WHERE chain=%s AND sha1=%s",
            [chain.id, sha1])

        first_line, last_line = cursor.fetchone()

        def readChunks():
            return [
                diff.Chunk(delete_offset,
                           delete_count,
                           insert_offset,
                           insert_count,
                           analysis=analysis,
                           is_whitespace=is_whitespace)
                for delete_offset, delete_count, insert_offset, insert_count,
                analysis, is_whitespace in cursor.fetchall()
            ]

        first_context_line = first_line - context_lines
        last_context_line = last_line + context_lines

        def includeChunk(chunk):
            if use == old:
                chunk_first_line, chunk_last_line = chunk.delete_offset, chunk.delete_offset + chunk.delete_count - 1
            else:
                chunk_first_line, chunk_last_line = chunk.insert_offset, chunk.insert_offset + chunk.insert_count - 1

            return chunk_last_line >= first_context_line and chunk_first_line <= last_context_line

        def lineFilter(line):
            if use == old:
                linenr = line.old_offset
                if linenr == first_context_line and line.type == diff.Line.INSERTED:
                    return False
            else:
                linenr = line.new_offset
                if linenr == first_context_line and line.type == diff.Line.DELETED:
                    return False

            return first_context_line <= linenr <= last_context_line

        file.loadOldLines(True)
        file.loadNewLines(True)

        context = diff.context.ContextLines(file, file.chunks,
                                            [(chain, use == old)])
        file.macro_chunks = context.getMacroChunks(context_lines,
                                                   highlight=True,
                                                   lineFilter=lineFilter)

        try:
            macro_chunk = file.macro_chunks[0]
        except:
            raise repr((parent.sha1, child.sha1))

        display_type = "both"

        if chain.state != "addressed":
            first_line_type = macro_chunk.lines[0].type
            if first_line_type == diff.Line.CONTEXT or (
                    use == old and first_line_type == diff.Line.DELETED) or (
                        use == new and first_line_type == diff.Line.INSERTED):
                for line in macro_chunk.lines[1:]:
                    if first_line_type != line.type:
                        break
                else:
                    display_type = "old" if use == old else "new"

        commit_url_component = "from=%s&to=%s" % (parent.sha1, child.sha1)

    def renderHeaderLeft(db, target, file):
        target.span("comment-chain-title").a(href="/showcomment?chain=%d" %
                                             chain.id).text(chain.title())

    def renderHeaderRight(db, target, file):
        side = use == old and "o" or "n"
        uri = "showcommit?%s&review=%d&file=%d#f%d%s%d" % (
            commit_url_component, review.id, file.id, file.id, side,
            first_line)
        target.span("filename").a(href=uri).text(file.path)

    def renderCommentsLocal(db, target, **kwargs):
        if display_type == "both":
            if use == old: position = "left"
            else: position = "right"
        else:
            position = "center"

        renderComments(db, target, user, chain, position, linkify)

    def lineId(base):
        return "c%d%s" % (chain.id, base)

    def lineCellId(base):
        return "c%d%s" % (chain.id, base)

    target.addInternalScript("commentChainById[%d] = %s;" %
                             (chain.id, chain.getJSConstructor(sha1)),
                             here=True)

    changeset_html.renderFile(db,
                              target,
                              user,
                              review,
                              file,
                              options={
                                  "support_expand": False,
                                  "display_type": display_type,
                                  "header_left": renderHeaderLeft,
                                  "header_right": renderHeaderRight,
                                  "content_after": renderCommentsLocal,
                                  "show": True,
                                  "expand": True,
                                  "line_id": lineId,
                                  "line_cell_id": lineCellId,
                                  "compact": compact,
                                  "tabify": tabify,
                                  "include_deleted": True
                              })

    data = (chain.id, file_id, use == old and "o"
            or "n", first_line, chain.id, file_id, use == old and "o"
            or "n", last_line, htmlutils.jsify(chain.type),
            htmlutils.jsify(chain.state), chain.id)

    target.addInternalScript("""$(document).ready(function ()
  {
    var markers = new CommentMarkers(null);
    markers.setLines(document.getElementById('c%df%d%s%d'), document.getElementById('c%df%d%s%d'));
    markers.setType(%s, %s);
    commentChainById[%d].markers = markers;
  });""" % data,
                             here=True)
Пример #42
0
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
Пример #43
0
def renderSearch(req, db, user):
    summary_value = req.getParameter("summary", None)
    summary_mode_value = req.getParameter("summarymode", None)
    branch_value = req.getParameter("branch", None)
    owner_value = req.getParameter("owner", None)
    path_value = req.getParameter("path", None)

    document = htmlutils.Document(req)
    document.setTitle("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.addInternalScript(user.getJS())

    cursor = db.cursor()
    cursor.execute("SELECT DISTINCT name, fullname FROM users JOIN reviewusers ON (reviewusers.uid=users.id) WHERE reviewusers.owner")

    users = [("{ label: %s, value: %s }" % (htmlutils.jsify("%s (%s)" % (fullname, name)),
                                            htmlutils.jsify(name)))
             for name, fullname in cursor]

    document.addInternalScript("var users = [ %s ];" % ", ".join(users))

    search = page.utils.PaleYellowTable(body, "Search")

    def renderSummary(target):
        target.input(name="summary", value=summary_value or "")
        summary_mode = target.select(name="summary_mode")
        summary_mode.option(value="all", selected="selected" if summary_mode_value == "all" else None).text("All words")
        summary_mode.option(value="any", selected="selected" if summary_mode_value == "any" else None).text("Any word")

    def renderBranch(target):
        target.input(name="branch", value=branch_value or "")

    def renderOwner(target):
        target.input(name="owner", value=owner_value or "")

    def renderPath(target):
        target.input(name="path", value=path_value or "")

    def renderButton(target):
        target.button(onclick="search();").text("Search")

    search.addItem("Summary", renderSummary, "Words occurring in the review's summary.")
    search.addItem("Branch", renderBranch, "Name of review branch.")
    search.addItem("Owner", renderOwner, "Owner of the review.")
    search.addItem("Path", renderPath, "Path (file or directory) that the review contains changes in.")
    search.addCentered(renderButton)

    if summary_value is not None: summary_value = summary_value.strip()
    if branch_value is not None: branch_value = branch_value.strip()
    if owner_value is not None: owner_value = owner_value.strip()
    if path_value is not None: path_value = path_value.strip()

    if summary_value or branch_value or owner_value or path_value:
        query = """SELECT DISTINCT reviews.id, reviews.summary, branches.name
                     FROM %s
                    WHERE %s"""

        tables = ["reviews", "branches ON (branches.id=reviews.branch)"]
        conditions = []
        arguments = []

        if summary_value:
            words = summary_value.split()
            operator = " AND " if summary_mode_value == "all" else " OR "
            conditions.append("(%s)" % operator.join(["reviews.summary ~* %s"] * len(words)))
            arguments.extend([".*\\m" + word + "\\M.*" for word in words])

        if branch_value:
            def globToSQLPattern(glob):
                pattern = glob.replace("\\", "\\\\").replace("%", "\\%").replace("?", "_").replace("*", "%")
                if pattern[0] != "%": pattern = "%" + pattern
                if pattern[-1] != "%": pattern = pattern + "%"
                return pattern

            conditions.append("branches.name LIKE %s")
            arguments.append(globToSQLPattern(branch_value))

        if owner_value:
            owner = dbutils.User.fromName(db, owner_value)
            tables.append("reviewusers ON (reviewusers.review=reviews.id)")
            conditions.append("reviewusers.uid=%s")
            conditions.append("reviewusers.owner")
            arguments.append(owner.id)

        if path_value:
            file_ids = dbutils.contained_files(db, dbutils.find_directory(db, path_value))

            if path_value[-1] != '/':
                file_ids.append(dbutils.find_file(db, path_value))

            tables.append("reviewfiles ON (reviewfiles.review=reviews.id)")
            conditions.append("reviewfiles.file=ANY (%s)")
            arguments.append(file_ids)

        query = """SELECT DISTINCT reviews.id, reviews.summary, branches.name
                     FROM %s
                    WHERE %s
                 ORDER BY reviews.id""" % (" JOIN ".join(tables), " AND ".join(conditions))

        cursor.execute(query, arguments)

        table = body.div("main").table("paleyellow reviews", align="center")
        table.col(width="20%")
        table.col(width="80%")
        header = table.tr().td("h1", colspan=4).h1()
        header.text("Reviews")

        for review_id, summary, branch_name in cursor:
            row = table.tr("review")
            row.td("name").text(branch_name)
            row.td("title").a(href="r/%d" % review_id).text(summary)

    return document
Пример #44
0
        def generateContent(self):
            trackedbranch = self.review.getTrackedBranch(self.db)

            if not trackedbranch:
                raise request.DisplayMessage("Not supported!", "The review r/%d is not tracking a remote branch." % self.review.id)

            self.document.addInternalScript(self.review.repository.getJS())
            self.document.addInternalScript(self.review.getJS())
            self.document.addInternalScript("var trackedbranch = { remote: %s, name: %s };"
                                            % (htmlutils.jsify(trackedbranch.remote),
                                               htmlutils.jsify(trackedbranch.name)))

            table = page.utils.PaleYellowTable(self.body, "Rebase tracking review")

            def renderRemote(target):
                target.span("value inset", id="remote").text(trackedbranch.remote)
            def renderCurrentBranch(target):
                target.span("value inset", id="currentbranch").text("refs/heads/" + trackedbranch.name)

            table.addItem("Remote", renderRemote)
            table.addItem("Current branch", renderCurrentBranch)
            table.addSeparator()

            if self.newbranch is not None and self.upstream is not None and self.newhead is not None and self.newupstream is not None:
                import log.html
                import log.commitset

                sha1s = self.review.repository.revlist(included=[self.newhead], excluded=[self.newupstream])
                new_commits = log.commitset.CommitSet(gitutils.Commit.fromSHA1(self.db, self.review.repository, sha1) for sha1 in sha1s)

                new_heads = new_commits.getHeads()
                if len(new_heads) != 1:
                    raise page.utils.DisplayMessage("Invalid commit-set!", "Multiple heads.  (This ought to be impossible...)")
                new_upstreams = new_commits.getFilteredTails(self.review.repository)
                if len(new_upstreams) != 1:
                    raise page.utils.DisplayMessage("Invalid commit-set!", "Multiple upstreams.  (This ought to be impossible...)")

                new_head = new_heads.pop()
                new_upstream_sha1 = new_upstreams.pop()

                old_commits = log.commitset.CommitSet(self.review.branch.getCommits(self.db))
                old_upstreams = old_commits.getFilteredTails(self.review.repository)

                if len(old_upstreams) != 1:
                    raise page.utils.DisplayMessage("Rebase not supported!", "The review has mulitple upstreams and can't be rebased.")

                if len(old_upstreams) == 1 and new_upstream_sha1 in old_upstreams:
                    # This appears to be a history rewrite.
                    new_upstream = None
                    new_upstream_sha1 = None
                    rebase_type = "history"
                else:
                    old_upstream = gitutils.Commit.fromSHA1(self.db, self.review.repository, old_upstreams.pop())
                    new_upstream = gitutils.Commit.fromSHA1(self.db, self.review.repository, new_upstream_sha1)

                    if old_upstream.isAncestorOf(new_upstream):
                        rebase_type = "move:ff"
                    else:
                        rebase_type = "move"

                self.document.addInternalScript("var check = { rebase_type: %s, old_head_sha1: %s, new_head_sha1: %s, new_upstream_sha1: %s, new_trackedbranch: %s };"
                                                % (htmlutils.jsify(rebase_type),
                                                   htmlutils.jsify(self.review.branch.head_sha1),
                                                   htmlutils.jsify(new_head.sha1),
                                                   htmlutils.jsify(new_upstream_sha1),
                                                   htmlutils.jsify(self.newbranch[len("refs/heads/"):])))

                def renderNewBranch(target):
                    target.span("value inset", id="newbranch").text(self.newbranch)
                    target.text(" @ ")
                    target.span("value inset").text(new_head.sha1[:8] + " " + new_head.niceSummary())
                def renderUpstream(target):
                    target.span("value inset", id="upstream").text(self.upstream)
                    target.text(" @ ")
                    target.span("value inset").text(new_upstream.sha1[:8] + " " + new_upstream.niceSummary())

                table.addItem("New branch", renderNewBranch)

                if new_upstream:
                    table.addItem("New upstream", renderUpstream)

                table.addSeparator()

                def renderMergeStatus(target):
                    target.a("status", id="status_merge").text("N/A")
                def renderConflictsStatus(target):
                    target.a("status", id="status_conflicts").text("N/A")
                def renderHistoryRewriteStatus(target):
                    target.a("status", id="status_historyrewrite").text("N/A")

                table.addSection("Status")

                if rebase_type == "history":
                    table.addItem("History rewrite", renderHistoryRewriteStatus)
                else:
                    if rebase_type == "move:ff":
                        table.addItem("Merge", renderMergeStatus)
                    table.addItem("Conflicts", renderConflictsStatus)

                def renderRebaseReview(target):
                    target.button(id="rebasereview", onclick="rebaseReview();", disabled="disabled").text("Rebase Review")

                table.addSeparator()
                table.addCentered(renderRebaseReview)

                log.html.render(self.db, self.body, "Rebased commits", commits=list(new_commits))
            else:
                try:
                    from customization.branches import getRebasedBranchPattern
                except ImportError:
                    def getRebasedBranchPattern(branch_name): return None

                pattern = getRebasedBranchPattern(trackedbranch.name)

                try:
                    from customization.branches import isRebasedBranchCandidate
                except ImportError:
                    isRebasedBranchCandidate = None

                if pattern or isRebasedBranchCandidate:
                    candidates = [name[len("refs/heads/"):]
                                  for sha1, name in gitutils.Repository.lsremote(trackedbranch.remote, pattern=pattern, include_heads=True)
                                  if name.startswith("refs/heads/")]

                    if isRebasedBranchCandidate is not None:
                        def isCandidate(name):
                            return isRebasedBranchCandidate(trackedbranch.name, name)

                        candidates = filter(isCandidate, candidates)
                else:
                    candidates = []

                if len(candidates) > 1:
                    def renderCandidates(target):
                        target.text("refs/heads/")
                        dropdown = target.select(id="newbranch")
                        for name in candidates:
                            dropdown.option(value=name).text(name)

                    table.addItem("New branch", renderCandidates,
                                    buttons=[("Edit", "editNewBranch(this);")])
                else:
                    if len(candidates) == 1:
                        default_value = candidates[0]
                    else:
                        default_value = trackedbranch.name

                    def renderEdit(target):
                        target.text("refs/heads/")
                        target.input(id="newbranch", value=default_value)

                    table.addItem("New branch", renderEdit)

                def renderUpstreamInput(target):
                    target.input(id="upstream", value="refs/heads/master")

                table.addItem("Upstream", renderUpstreamInput)

                def renderFetchBranch(target):
                    target.button(onclick="fetchBranch();").text("Fetch Branch")

                table.addSeparator()
                table.addCentered(renderFetchBranch)
Пример #45
0
def renderHome(req, db, user):
    if user.isAnonymous(): raise page.utils.NeedLogin, req

    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))

    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()

    page.utils.generateHeader(body, db, user, current_page="home")

    document.addExternalStylesheet("resource/home.css")
    document.addExternalScript("resource/home.js")
    if repository: document.addInternalScript(repository.getJS())
    else: document.addInternalScript("var repository = null;")
    if user.name != req.user and req.getUser(db).hasRole(db, "administrator"):
        document.addInternalScript("var administrator = true;")
    else:
        document.addInternalScript("var administrator = false;")
    document.addInternalScript(user.getJS())
    document.addInternalScript("user.gitEmails = %s;" %
                               htmlutils.jsify(gitemails))
    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, status_id=None):
        main_row = basic.tr('line')
        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")
            target.button(onclick="saveFullname();").text("Save")
            target.button(onclick="resetFullname();").text("Reset")

    def renderEmail(target):
        if readonly: target.text(user.email)
        else:
            target.input("value", id="user_email", value=user.email)
            target.span("status", id="status_email")
            target.button(onclick="saveEmail();").text("Save")
            target.button(onclick="resetEmail();").text("Reset")

    def renderGitEmails(target):
        if readonly: target.text(gitemails)
        else:
            target.input("value", id="user_gitemails", value=gitemails)
            target.span("status", id="status_gitemails")
            target.button(onclick="saveGitEmails();").text("Save")
            target.button(onclick="resetGitEmails();").text("Reset")

    def renderPassword(target):
        target.text("****")
        if not readonly:
            target.button(onclick="changePassword();").text("Change")

    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.",
        status_id="status_fullname")
    row("Email",
        renderEmail,
        "This is the primary email address, to which emails are sent.",
        status_id="status_email")
    row("Git Emails",
        renderGitEmails,
        "These email addresses are used to map Git commits to the user.",
        status_id="status_gitemails")

    if configuration.base.AUTHENTICATION_MODE == "critic":
        row("Password", renderPassword)

    filters = target.table('paleyellow filters', align='center')
    row = filters.tr()
    row.td("h1", colspan=2).h1().text("Filters")
    repositories = row.td("repositories", colspan=2).select()

    if not repository:
        repositories.option(value="-",
                            selected="selected",
                            disabled="disabled").text("Select a repository")

    cursor.execute("SELECT id, path FROM repositories ORDER BY id")
    for id, path in cursor:
        repositories.option(value=id,
                            selected="selected" if repository
                            and id == repository.id else None).text(
                                "%s:%s" % (configuration.base.HOSTNAME, path))

    help = filters.tr().td("help", colspan=4)

    help.p().text(
        "Filters is the system's mechanism to connect reviews to users.  When a review is created or updated, a set of users to associate with the review is calculated by matching the files modified by each commit in the review to the filters set up by users.  Each filter selects one file or one directory (and affects all sub-directories and files,) and only the most specific filter per file and user is used when associating users with reviews."
    )

    p = help.p()
    p.text("There are two types of filters: ")
    p.code().text("reviewer")
    p.text(" and ")
    p.code().text("watcher")
    p.text(".  All files matched by a ")
    p.code().text("reviewer")
    p.text(
        " filter for a user are added to the user's to-do list, meaning the user needs to review all changes made to that file before the review is finished.  However, if more than one user is matched as a reviewer for a file, only one of them needs to review the changes.  A user associated with a review only by "
    )
    p.code().text("watcher")
    p.text(
        " filters will simply receive notifications relating to the review, but isn't required to do anything."
    )

    p = help.p()
    p.text("For a ")
    p.code().text("reviewer")
    p.text(
        " type filter, a set of \"delegates\" can also be defined.  The delegate field should be a comma-separated list of user names.  Delegates are automatically made reviewers of changes by you in the filtered files (since you can't review them yourself) regardless of their own filters."
    )

    p = help.p()
    p.strong().text(
        "Note: A filter names a directory only if the path ends with a slash ('/')."
    )
    p.text(
        "  If the path doesn't end with a slash, the filter would name a specific file even if the path is a directory in some or all versions of the actual tree.  However, you'll get a warning if you try to add a filter for a file whose path is registered as a directory in the database."
    )

    if repository:
        headings = filters.tr("headings")
        headings.td("heading type").text("Type")
        headings.td("heading path").text("Path")
        headings.td("heading delegate").text("Delegate")
        headings.td("heading buttons")

        cursor.execute(
            "SELECT directory, file, type, delegate FROM filters WHERE uid=%s AND repository=%s",
            [user.id, repository.id])

        all_filters = []

        for directory_id, file_id, filter_type, delegate in cursor.fetchall():
            if file_id == 0:
                path = dbutils.describe_directory(db, directory_id) + "/"
            else:
                path = dbutils.describe_file(db, file_id)
            all_filters.append(
                (path, directory_id, file_id, filter_type, delegate))

        all_filters.sort()

        empty = filters.tr("empty").td(
            "empty", colspan=4).span(id="empty").text("No filters configured")
        if filters: empty.setAttribute("style", "display: none")

        for path, directory_id, file_id, filter_type, delegate in all_filters:
            row = filters.tr("filter")
            row.td("filter type").text(filter_type.capitalize())
            row.td("filter path").text(path)
            row.td("filter delegate").text(delegate)

            buttons = row.td("filter buttons")
            if readonly: buttons.text()
            else:
                buttons.button(onclick="editFilter(this, %d, %d, false);" %
                               (directory_id, file_id)).text("Edit")
                buttons.button(onclick="deleteFilter(this, %d, %d);" %
                               (directory_id, file_id)).text("Delete")

        if not readonly:
            filters.tr("buttons").td("buttons", colspan=4).button(
                onclick="addFilter(this);").text("Add Filter")

    return document
Пример #46
0
        def generateContent(self):
            trackedbranch = self.review.getTrackedBranch(self.db)

            if not trackedbranch:
                raise request.DisplayMessage("Not supported!", "The review r/%d is not tracking a remote branch." % self.review.id)

            self.document.addInternalScript(self.review.repository.getJS())
            self.document.addInternalScript(self.review.getJS())
            self.document.addInternalScript("var trackedbranch = { remote: %s, name: %s };"
                                            % (htmlutils.jsify(trackedbranch.remote),
                                               htmlutils.jsify(trackedbranch.name)))

            table = page.utils.PaleYellowTable(self.body, "Rebase tracking review")

            def renderRemote(target):
                target.span("value inset", id="remote").text(trackedbranch.remote)
            def renderCurrentBranch(target):
                target.span("value inset", id="currentbranch").text("refs/heads/" + trackedbranch.name)

            table.addItem("Remote", renderRemote)
            table.addItem("Current branch", renderCurrentBranch)
            table.addSeparator()

            if self.newbranch is not None and self.upstream is not None and self.newhead is not None and self.newupstream is not None:
                import log.html
                import log.commitset

                sha1s = self.review.repository.revlist(included=[self.newhead], excluded=[self.newupstream])
                new_commits = log.commitset.CommitSet(gitutils.Commit.fromSHA1(self.db, self.review.repository, sha1) for sha1 in sha1s)

                new_heads = new_commits.getHeads()
                if len(new_heads) != 1:
                    raise page.utils.DisplayMessage("Invalid commit-set!", "Multiple heads.  (This ought to be impossible...)")
                new_upstreams = new_commits.getFilteredTails(self.review.repository)
                if len(new_upstreams) != 1:
                    raise page.utils.DisplayMessage("Invalid commit-set!", "Multiple upstreams.  (This ought to be impossible...)")

                new_head = new_heads.pop()
                new_upstream_sha1 = new_upstreams.pop()

                old_commits = log.commitset.CommitSet(self.review.branch.commits)
                old_upstreams = old_commits.getFilteredTails(self.review.repository)

                if len(old_upstreams) != 1:
                    raise page.utils.DisplayMessage("Rebase not supported!", "The review has mulitple upstreams and can't be rebased.")

                if len(old_upstreams) == 1 and new_upstream_sha1 in old_upstreams:
                    # This appears to be a history rewrite.
                    new_upstream = None
                    new_upstream_sha1 = None
                    rebase_type = "history"
                else:
                    old_upstream = gitutils.Commit.fromSHA1(self.db, self.review.repository, old_upstreams.pop())
                    new_upstream = gitutils.Commit.fromSHA1(self.db, self.review.repository, new_upstream_sha1)

                    if old_upstream.isAncestorOf(new_upstream):
                        rebase_type = "move:ff"
                    else:
                        rebase_type = "move"

                self.document.addInternalScript("var check = { rebase_type: %s, old_head_sha1: %s, new_head_sha1: %s, new_upstream_sha1: %s, new_trackedbranch: %s };"
                                                % (htmlutils.jsify(rebase_type),
                                                   htmlutils.jsify(self.review.branch.head.sha1),
                                                   htmlutils.jsify(new_head.sha1),
                                                   htmlutils.jsify(new_upstream_sha1),
                                                   htmlutils.jsify(self.newbranch[len("refs/heads/"):])))

                def renderNewBranch(target):
                    target.span("value inset", id="newbranch").text(self.newbranch)
                    target.text(" @ ")
                    target.span("value inset").text(new_head.sha1[:8] + " " + new_head.niceSummary())
                def renderUpstream(target):
                    target.span("value inset", id="upstream").text(self.upstream)
                    target.text(" @ ")
                    target.span("value inset").text(new_upstream.sha1[:8] + " " + new_upstream.niceSummary())

                table.addItem("New branch", renderNewBranch)

                if new_upstream:
                    table.addItem("New upstream", renderUpstream)

                table.addSeparator()

                def renderMergeStatus(target):
                    target.a("status", id="status_merge").text("N/A")
                def renderConflictsStatus(target):
                    target.a("status", id="status_conflicts").text("N/A")
                def renderHistoryRewriteStatus(target):
                    target.a("status", id="status_historyrewrite").text("N/A")

                table.addSection("Status")

                if rebase_type == "history":
                    table.addItem("History rewrite", renderHistoryRewriteStatus)
                else:
                    if rebase_type == "move:ff":
                        table.addItem("Merge", renderMergeStatus)
                    table.addItem("Conflicts", renderConflictsStatus)

                def renderRebaseReview(target):
                    target.button(id="rebasereview", onclick="rebaseReview();", disabled="disabled").text("Rebase Review")

                table.addSeparator()
                table.addCentered(renderRebaseReview)

                log.html.render(self.db, self.body, "Rebased commits", commits=list(new_commits))
            else:
                try:
                    from customization.branches import getRebasedBranchPattern
                except ImportError:
                    def getRebasedBranchPattern(branch_name): return None

                pattern = getRebasedBranchPattern(trackedbranch.name)

                try:
                    from customization.branches import isRebasedBranchCandidate
                except ImportError:
                    isRebasedBranchCandidate = None

                if pattern or isRebasedBranchCandidate:
                    candidates = [name[len("refs/heads/"):]
                                  for sha1, name in gitutils.Repository.lsremote(trackedbranch.remote, pattern=pattern, include_heads=True)
                                  if name.startswith("refs/heads/")]

                    if isRebasedBranchCandidate is not None:
                        def isCandidate(name):
                            return isRebasedBranchCandidate(trackedbranch.name, name)

                        candidates = filter(isCandidate, candidates)
                else:
                    candidates = []

                if len(candidates) > 1:
                    def renderCandidates(target):
                        target.text("refs/heads/")
                        dropdown = target.select(id="newbranch")
                        for name in candidates:
                            dropdown.option(value=name).text(name)

                    table.addItem("New branch", renderCandidates,
                                    buttons=[("Edit", "editNewBranch(this);")])
                else:
                    if len(candidates) == 1:
                        default_value = candidates[0]
                    else:
                        default_value = trackedbranch.name

                    def renderEdit(target):
                        target.text("refs/heads/")
                        target.input(id="newbranch", value=default_value)

                    table.addItem("New branch", renderEdit)

                def renderUpstreamInput(target):
                    target.input(id="upstream", value="refs/heads/master")

                table.addItem("Upstream", renderUpstreamInput)

                def renderFetchBranch(target):
                    target.button(onclick="fetchBranch();").text("Fetch Branch")

                table.addSeparator()
                table.addCentered(renderFetchBranch)
Пример #47
0
    def renderWatchers(target):
        if review.watchers:
            for index, watcher in enumerate(review.watchers):
                if index != 0: target.text(", ")
                span = target.span("user %s" % watcher.status)
                span.span("name").text(watcher.fullname)
                if watcher.status == 'absent':
                    span.span("status").text(" (%s)" % watcher.getAbsence(db))
                elif watcher.status == 'retired':
                    span.span("status").text(" (retired)")
        else:
            target.i().text("No watchers.")

        cursor.execute("""SELECT reviewfilters.id, reviewfilters.uid, reviewfilters.directory, reviewfilters.file
                            FROM reviewfilters
                            JOIN users ON (reviewfilters.uid=users.id)
                           WHERE reviewfilters.review=%s
                             AND reviewfilters.type='watcher'
                             AND users.status!='retired'""",
                       (review.id,))

        rows = cursor.fetchall()
        watcher_filters_hidden = []

        if rows:
            table = target.table("reviewfilters watchers")

            row = table.thead().tr("h1")
            row.th("h1", colspan=4).text("Custom filters:")

            filter_data = {}
            reviewfilters = {}

            for filter_id, user_id, directory_id, file_id in rows:
                filter_user = dbutils.User.fromId(db, user_id)

                if file_id: path = dbutils.describe_file(db, file_id)
                else: path = dbutils.describe_directory(db, directory_id) + "/"

                reviewfilters.setdefault(filter_user.fullname, []).append(path)
                filter_data[(filter_user.fullname, path)] = (filter_id, filter_user)

            count = 0
            tbody = table.tbody()

            for fullname in sorted(reviewfilters.keys()):
                original_paths = sorted(reviewfilters[fullname])
                trimmed_paths = diff.File.eliminateCommonPrefixes(original_paths[:])

                first = True

                for original_path, trimmed_path in zip(original_paths, trimmed_paths):
                    row = tbody.tr("filter")

                    if first:
                        row.td("username", rowspan=len(original_paths)).text(fullname)
                        row.td("reviews", rowspan=len(original_paths)).text("watches")
                        first = False

                    row.td("path").span().innerHTML(trimmed_path)

                    filter_id, filter_user = filter_data[(fullname, original_path)]

                    href = "javascript:removeReviewFilter(%d, %s, 'watcher', %s, %s);" % (filter_id, filter_user.getJSConstructor(), htmlutils.jsify(original_path), "true" if filter_user != user else "false")
                    row.td("remove").a(href=href).text("[remove]")

                    count += 1

            tfoot = table.tfoot()
            tfoot.tr().td(colspan=4).text("%d line%s hidden" % (count, "s" if count > 1 else ""))

            if count > 10:
                tbody.setAttribute("class", "hidden")
                watcher_filters_hidden.append(True)
            else:
                tfoot.setAttribute("class", "hidden")
                watcher_filters_hidden.append(False)

        buttons = target.div("buttons")

        if watcher_filters_hidden:
            buttons.button("showfilters", onclick="toggleReviewFilters('watchers', $(this));").text("%s Custom Filters" % ("Show" if watcher_filters_hidden[0] else "Hide"))

        buttons.button("addwatcher", onclick="addWatcher();").text("Add Watcher")

        if user not in review.reviewers and user not in review.owners:
            if user not in review.watchers:
                buttons.button("watch", onclick="watchReview();").text("Watch Review")
            elif review.watchers[user] == "manual":
                buttons.button("watch", onclick="unwatchReview();").text("Stop Watching Review")
Пример #48
0
    def renderWatchers(target):
        if review.watchers:
            for index, watcher in enumerate(review.watchers):
                if index != 0: target.text(", ")
                span = target.span("user %s" % watcher.status)
                span.span("name").text(watcher.fullname)
                if watcher.status == 'absent':
                    span.span("status").text(" (%s)" % watcher.getAbsence(db))
                elif watcher.status == 'retired':
                    span.span("status").text(" (retired)")
        else:
            target.i().text("No watchers.")

        cursor.execute("""SELECT reviewfilters.id, reviewfilters.uid, reviewfilters.path
                            FROM reviewfilters
                            JOIN users ON (reviewfilters.uid=users.id)
                           WHERE reviewfilters.review=%s
                             AND reviewfilters.type='watcher'
                             AND users.status!='retired'""",
                       (review.id,))

        rows = cursor.fetchall()
        watcher_filters_hidden = []

        if rows:
            table = target.table("reviewfilters watchers")

            row = table.thead().tr("h1")
            row.th("h1", colspan=4).text("Custom filters:")

            filter_data = {}
            reviewfilters = {}

            for filter_id, user_id, path in rows:
                filter_user = dbutils.User.fromId(db, user_id)
                reviewfilters.setdefault(filter_user.fullname, []).append(path or "/")
                filter_data[(filter_user.fullname, path)] = (filter_id, filter_user)

            count = 0
            tbody = table.tbody()

            for fullname in sorted(reviewfilters.keys()):
                original_paths = sorted(reviewfilters[fullname])
                trimmed_paths = diff.File.eliminateCommonPrefixes(original_paths[:])

                first = True

                for original_path, trimmed_path in zip(original_paths, trimmed_paths):
                    row = tbody.tr("filter")

                    if first:
                        row.td("username", rowspan=len(original_paths)).text(fullname)
                        row.td("reviews", rowspan=len(original_paths)).text("watches")
                        first = False

                    row.td("path").span().innerHTML(trimmed_path)

                    filter_id, filter_user = filter_data[(fullname, original_path)]

                    href = "javascript:removeReviewFilter(%d, %s, 'watcher', %s, %s);" % (filter_id, filter_user.getJSConstructor(), htmlutils.jsify(original_path), "true" if filter_user != user else "false")
                    row.td("remove").a(href=href).text("[remove]")

                    count += 1

            tfoot = table.tfoot()
            tfoot.tr().td(colspan=4).text("%d line%s hidden" % (count, "s" if count > 1 else ""))

            if count > 10:
                tbody.setAttribute("class", "hidden")
                watcher_filters_hidden.append(True)
            else:
                tfoot.setAttribute("class", "hidden")
                watcher_filters_hidden.append(False)

        buttons = target.div("buttons")

        if watcher_filters_hidden:
            buttons.button("showfilters", onclick="toggleReviewFilters('watchers', $(this));").text("%s Custom Filters" % ("Show" if watcher_filters_hidden[0] else "Hide"))

        buttons.button("addwatcher", onclick="addWatcher();").text("Add Watcher")

        if user not in review.reviewers and user not in review.owners:
            if user not in review.watchers:
                buttons.button("watch", onclick="watchReview();").text("Watch Review")
            elif review.watchers[user] == "manual":
                buttons.button("watch", onclick="unwatchReview();").text("Stop Watching Review")
Пример #49
0
def renderHome(req, db, user):
    if user.isAnonymous():
        raise page.utils.NeedLogin, req

    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))

    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()

    page.utils.generateHeader(body, db, user, current_page="home")

    document.addExternalStylesheet("resource/home.css")
    document.addExternalScript("resource/home.js")
    if repository:
        document.addInternalScript(repository.getJS())
    else:
        document.addInternalScript("var repository = null;")
    if user.name != req.user and req.getUser(db).hasRole(db, "administrator"):
        document.addInternalScript("var administrator = true;")
    else:
        document.addInternalScript("var administrator = false;")
    document.addInternalScript(user.getJS())
    document.addInternalScript("user.gitEmails = %s;" % htmlutils.jsify(gitemails))
    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, status_id=None):
        main_row = basic.tr("line")
        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")
            target.button(onclick="saveFullname();").text("Save")
            target.button(onclick="resetFullname();").text("Reset")

    def renderEmail(target):
        if readonly:
            target.text(user.email)
        else:
            target.input("value", id="user_email", value=user.email)
            target.span("status", id="status_email")
            target.button(onclick="saveEmail();").text("Save")
            target.button(onclick="resetEmail();").text("Reset")

    def renderGitEmails(target):
        if readonly:
            target.text(gitemails)
        else:
            target.input("value", id="user_gitemails", value=gitemails)
            target.span("status", id="status_gitemails")
            target.button(onclick="saveGitEmails();").text("Save")
            target.button(onclick="resetGitEmails();").text("Reset")

    def renderPassword(target):
        target.text("****")
        if not readonly:
            target.button(onclick="changePassword();").text("Change")

    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.",
        status_id="status_fullname",
    )
    row("Email", renderEmail, "This is the primary email address, to which emails are sent.", status_id="status_email")
    row(
        "Git Emails",
        renderGitEmails,
        "These email addresses are used to map Git commits to the user.",
        status_id="status_gitemails",
    )

    if configuration.base.AUTHENTICATION_MODE == "critic":
        row("Password", renderPassword)

    filters = target.table("paleyellow filters", align="center")
    row = filters.tr()
    row.td("h1", colspan=2).h1().text("Filters")
    repositories = row.td("repositories", colspan=2).select()

    if not repository:
        repositories.option(value="-", selected="selected", disabled="disabled").text("Select a repository")

    cursor.execute("SELECT id, path FROM repositories ORDER BY id")
    for id, path in cursor:
        repositories.option(value=id, selected="selected" if repository and id == repository.id else None).text(
            "%s:%s" % (configuration.base.HOSTNAME, path)
        )

    help = filters.tr().td("help", colspan=4)

    help.p().text(
        "Filters is the system's mechanism to connect reviews to users.  When a review is created or updated, a set of users to associate with the review is calculated by matching the files modified by each commit in the review to the filters set up by users.  Each filter selects one file or one directory (and affects all sub-directories and files,) and only the most specific filter per file and user is used when associating users with reviews."
    )

    p = help.p()
    p.text("There are two types of filters: ")
    p.code().text("reviewer")
    p.text(" and ")
    p.code().text("watcher")
    p.text(".  All files matched by a ")
    p.code().text("reviewer")
    p.text(
        " filter for a user are added to the user's to-do list, meaning the user needs to review all changes made to that file before the review is finished.  However, if more than one user is matched as a reviewer for a file, only one of them needs to review the changes.  A user associated with a review only by "
    )
    p.code().text("watcher")
    p.text(" filters will simply receive notifications relating to the review, but isn't required to do anything.")

    p = help.p()
    p.text("For a ")
    p.code().text("reviewer")
    p.text(
        ' type filter, a set of "delegates" can also be defined.  The delegate field should be a comma-separated list of user names.  Delegates are automatically made reviewers of changes by you in the filtered files (since you can\'t review them yourself) regardless of their own filters.'
    )

    p = help.p()
    p.strong().text("Note: A filter names a directory only if the path ends with a slash ('/').")
    p.text(
        "  If the path doesn't end with a slash, the filter would name a specific file even if the path is a directory in some or all versions of the actual tree.  However, you'll get a warning if you try to add a filter for a file whose path is registered as a directory in the database."
    )

    if repository:
        headings = filters.tr("headings")
        headings.td("heading type").text("Type")
        headings.td("heading path").text("Path")
        headings.td("heading delegate").text("Delegate")
        headings.td("heading buttons")

        cursor.execute(
            "SELECT directory, file, type, delegate FROM filters WHERE uid=%s AND repository=%s",
            [user.id, repository.id],
        )

        all_filters = []

        for directory_id, file_id, filter_type, delegate in cursor.fetchall():
            if file_id == 0:
                path = dbutils.describe_directory(db, directory_id) + "/"
            else:
                path = dbutils.describe_file(db, file_id)
            all_filters.append((path, directory_id, file_id, filter_type, delegate))

        all_filters.sort()

        empty = filters.tr("empty").td("empty", colspan=4).span(id="empty").text("No filters configured")
        if filters:
            empty.setAttribute("style", "display: none")

        for path, directory_id, file_id, filter_type, delegate in all_filters:
            row = filters.tr("filter")
            row.td("filter type").text(filter_type.capitalize())
            row.td("filter path").text(path)
            row.td("filter delegate").text(delegate)

            buttons = row.td("filter buttons")
            if readonly:
                buttons.text()
            else:
                buttons.button(onclick="editFilter(this, %d, %d, false);" % (directory_id, file_id)).text("Edit")
                buttons.button(onclick="deleteFilter(this, %d, %d);" % (directory_id, file_id)).text("Delete")

        if not readonly:
            filters.tr("buttons").td("buttons", colspan=4).button(onclick="addFilter(this);").text("Add Filter")

    return document
Пример #50
0
def generateDataScript(db, user, changeset, review, file_id_format, compact, parent_index):
    data = { 'parent_id': changeset.parent.id if changeset.parent else None,
             'parent_sha1': htmlutils.jsify(changeset.parent.sha1) if changeset.parent else None,
             'child_id': changeset.child.id,
             'child_sha1': htmlutils.jsify(changeset.child.sha1),
             'changeset_id': jsify(changeset.id),
             'commit_ids': ", ".join([str(commit.getId(db)) for commit in reversed(changeset.commits(db))]),
             'parent_index': parent_index }

    if parent_index is None:
        commits = changeset.commits(db)

        if review and commits:
            if len(commits) > 1:
                cursor = db.cursor()
                cursor.execute("SELECT id FROM changesets JOIN reviewchangesets ON (changeset=id) WHERE review=%s AND child=ANY (%s)",
                               (review.id, [commit.getId(db) for commit in commits]))

                changeset_ids = [changeset_id for (changeset_id,) in cursor]
            else:
                changeset_ids = [changeset.id]
        else:
            changeset_ids = None

        if changeset_ids is None: changeset_ids = "null"
        else: changeset_ids = "[%s]" % ", ".join(map(str, changeset_ids))

        data["changeset_ids"] = changeset_ids

        if changeset.parent:
            data_script = """
var changeset = { parent: { id: %(parent_id)d, sha1: %(parent_sha1)s },
                  child: { id: %(child_id)d, sha1: %(child_sha1)s },
                  id: %(changeset_id)s,
                  ids: %(changeset_ids)s,
                  commits: [ %(commit_ids)s ] };
var useFiles = files;
""" % data
        else:
            data_script = """
var changeset = { parent: null,
                  child: { id: %(child_id)d, sha1: %(child_sha1)s },
                  id: %(changeset_id)s,
                  ids: %(changeset_ids)s,
                  commits: [ %(commit_ids)s ] };
var useFiles = files;
""" % data
    else:
        data_script = """
var changeset;
var parent_index = %(parent_index)d;

if (!changeset)
  changeset = { commits: [ %(child_id)d ] };

if (!changeset[parent_index])
  changeset[parent_index] = {};

if (!changeset.child)
  changeset.child = { id: %(child_id)d, sha1: %(child_sha1)s };

changeset[parent_index].parent = { id: %(parent_id)d, sha1: %(parent_sha1)s };
changeset[parent_index].child = { id: %(child_id)d, sha1: %(child_sha1)s };
changeset[parent_index].id = %(changeset_id)s;

var useFiles = files[parent_index] = {};
""" % data

    parent_index_property = "parent: %d, " % parent_index if parent_index is not None else ""

    for file in changeset.files:
        if file.hasChanges():
            data_script += """
useFiles[%d] = { old_sha1: %r,
               %snew_sha1: %r,
               %spath: %s };
""" % (file.id, file.old_sha1, " " * len(str(file.id)), file.new_sha1, " " * len(str(file.id)), jsify(file.path))
            if file.old_sha1 != "0" * 40 and file.new_sha1 != "0" * 40:
                data_script += """files[%r] = { %sid: %d, side: 'o' };
""" % (file.old_sha1, parent_index_property, file.id)
                data_script += """files[%r] = { id: %d, side: 'n' };
""" % (file.new_sha1, file.id)
            elif file.new_sha1 != "0" * 40:
                data_script += """files[%r] = { id: %d, side: 'n' };
""" % (file.new_sha1, file.id)
            else:
                data_script += """files[%r] = { %sid: %d, side: 'o' };
""" % (file.old_sha1, parent_index_property, file.id)

    if review:
        data_script += """
%s
var commentChains;
""" % review.getJS()

        reviewable_files = ", ".join([("%d: %r" % (file_id, state)) for file_id, (is_reviewer, state, reviewers) in changeset.getReviewFiles(db, user, review).items() if is_reviewer])

        if parent_index is None:
            data_script += """
changeset.reviewableFiles = { %s };
""" % reviewable_files
        else:
            data_script += """
changeset[parent_index].reviewableFiles = { %s };
""" % reviewable_files

    if compact: return re.sub(r"\B\s+\B|\b\s+\B|\B\s+\b", "", data_script).strip()
    else: return data_script.strip()
Пример #51
0
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)) + "&#8230;/" + 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)) + "&#8230;/" + 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
Пример #52
0
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)) +
                                                        "&#8230;/" + 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)) + "&#8230;/" +
                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
Пример #53
0
    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(" ", "&nbsp;")

        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]")
Пример #54
0
def renderCodeCommentChain(db, target, user, review, chain, context_lines=3, compact=False, tabify=False, original=False, changeset=None, linkify=False):
    repository = review.repository

    old_sha1 = None
    new_sha1 = None

    old = 1
    new = 2

    cursor = db.cursor()

    file_id = chain.file_id
    file_path = dbutils.describe_file(db, file_id)

    if (chain.state != "addressed" or original) and chain.first_commit == chain.last_commit:
        sha1 = chain.first_commit.getFileSHA1(file_path)

        cursor.execute("SELECT first_line, last_line FROM commentchainlines WHERE chain=%s AND sha1=%s", (chain.id, sha1))
        first_line, last_line = cursor.fetchone()

        file = diff.File(file_id, file_path, sha1, sha1, review.repository, chunks=[])
        file.loadNewLines(True)

        start = max(1, first_line - context_lines)
        end = min(file.newCount(), last_line + context_lines)
        count = end + 1 - start

        lines = file.newLines(True)
        lines = [diff.Line(diff.Line.CONTEXT, start + index, lines[start + index - 1], start + index, lines[start + index - 1]) for index in range(count)]

        file.macro_chunks = [diff.MacroChunk([], lines)]

        use = new
        display_type = "new"
        commit_url_component = "sha1=%s" % chain.first_commit.sha1
    else:
        if chain.state == "addressed" and not original and review.containsCommit(db, chain.addressed_by):
            parent = gitutils.Commit.fromSHA1(db, review.repository, chain.addressed_by.parents[0])
            child = chain.addressed_by
            use = old
        else:
            parent = chain.first_commit
            child = chain.last_commit

            if parent == child:
                if chain.origin == "old":
                    cursor.execute("""SELECT changesets.child
                                        FROM changesets, reviewchangesets
                                       WHERE changesets.parent=%s
                                         AND reviewchangesets.changeset=changesets.id
                                         AND reviewchangesets.review=%s""",
                                   [child.getId(db), review.id])

                    try:
                        child = gitutils.Commit.fromId(db, repository, cursor.fetchone()[0])
                    except:
                        parent = gitutils.Commit.fromSHA1(db, repository, child.parents[0])
                else:
                    parent = gitutils.Commit.fromSHA1(db, repository, child.parents[0])

            if chain.origin == "old": use = old
            else: use = new

        if parent.sha1 in child.parents and len(child.parents) == 1:
            commit = child
            from_commit = None
            to_commit = None
        else:
            commit = None
            from_commit = parent
            to_commit = child

        if changeset:
            assert ((changeset.parent == from_commit and changeset.child == to_commit)
                    if commit is None else
                    (changeset.parent.sha1 == commit.parents[0] and changeset.child == commit))
            assert changeset.getFile(file_id)
        else:
            changeset = changeset_utils.createChangeset(db, user, repository, commit=commit, from_commit=from_commit, to_commit=to_commit, filtered_file_ids=set((file_id,)))[0]

        file = changeset.getFile(file_id)

        if not file:
            if chain.state == "addressed" and not original:
                renderCodeCommentChain(db, target, user, review, chain, context_lines, compact, tabify, original=True)
                return
            else:
                raise

        # Commit so that the diff and its analysis, written to the database by createChangeset(),
        # can be reused later.
        db.commit()

        old_sha1 = file.old_sha1
        new_sha1 = file.new_sha1

        if use == old and old_sha1 == '0' * 40: use = new
        elif use == new and new_sha1 == '0' * 40: use = old

        if use == old: sha1 = old_sha1
        else: sha1 = new_sha1

        cursor.execute("SELECT first_line, last_line FROM commentchainlines WHERE chain=%s AND sha1=%s", [chain.id, sha1])

        first_line, last_line = cursor.fetchone()

        def readChunks():
            return [diff.Chunk(delete_offset, delete_count, insert_offset, insert_count, analysis=analysis, is_whitespace=is_whitespace)
                    for delete_offset, delete_count, insert_offset, insert_count, analysis, is_whitespace
                    in cursor.fetchall()]

        first_context_line = first_line - context_lines
        last_context_line = last_line + context_lines

        def includeChunk(chunk):
            if use == old: chunk_first_line, chunk_last_line = chunk.delete_offset, chunk.delete_offset + chunk.delete_count - 1
            else: chunk_first_line, chunk_last_line = chunk.insert_offset, chunk.insert_offset + chunk.insert_count - 1

            return chunk_last_line >= first_context_line and chunk_first_line <= last_context_line

        def lineFilter(line):
            if use == old:
                linenr = line.old_offset
                if linenr == first_context_line and line.type == diff.Line.INSERTED:
                    return False
            else:
                linenr = line.new_offset
                if linenr == first_context_line and line.type == diff.Line.DELETED:
                    return False

            return first_context_line <= linenr <= last_context_line

        file.loadOldLines(True)
        file.loadNewLines(True)

        context = diff.context.ContextLines(file, file.chunks, [chain])
        file.macro_chunks = context.getMacroChunks(context_lines, highlight=True, lineFilter=lineFilter)

        try: macro_chunk = file.macro_chunks[0]
        except: raise repr((parent.sha1, child.sha1))

        display_type = "both"

        if chain.state != "addressed":
            first_line_type = macro_chunk.lines[0].type
            if first_line_type == diff.Line.CONTEXT or (use == old and first_line_type == diff.Line.DELETED) or (use == new and first_line_type == diff.Line.INSERTED):
                for line in macro_chunk.lines[1:]:
                    if first_line_type != line.type:
                        break
                else:
                    display_type = "old" if use == old else "new"

        commit_url_component = "from=%s&to=%s" % (parent.sha1, child.sha1)

    def renderHeaderLeft(db, target, file):
        target.span("comment-chain-title").a(href="/showcomment?chain=%d" % chain.id).text(chain.title())

    def renderHeaderRight(db, target, file):
        side = use == old and "o" or "n"
        uri = "showcommit?%s&review=%d&file=%d#f%d%s%d" % (commit_url_component, review.id, file.id, file.id, side, first_line)
        target.span("filename").a(href=uri).text(file.path)

    def renderCommentsLocal(db, target, **kwargs):
        if display_type == "both":
            if use == old: position = "left"
            else: position = "right"
        else:
            position = "center"

        renderComments(db, target, user, chain, position, linkify)

    def lineId(base):
        return "c%d%s" % (chain.id, base)

    def lineCellId(base):
        return "c%d%s" % (chain.id, base)

    target.addInternalScript("commentChainById[%d] = %s;" % (chain.id, chain.getJSConstructor(sha1)), here=True)

    changeset_html.renderFile(db, target, user, review, file, options={ "support_expand": False, "display_type": display_type, "header_left": renderHeaderLeft, "header_right": renderHeaderRight, "content_after": renderCommentsLocal, "show": True, "expand": True, "line_id": lineId, "line_cell_id": lineCellId, "compact": compact, "tabify": tabify, "include_deleted": True })

    data = (chain.id, file_id, use == old and "o" or "n", first_line,
            chain.id, file_id, use == old and "o" or "n", last_line,
            htmlutils.jsify(chain.type), htmlutils.jsify(chain.state),
            chain.id)

    target.addInternalScript("""$(document).ready(function ()
  {
    var markers = new CommentMarkers(null);
    markers.setLines(document.getElementById('c%df%d%s%d'), document.getElementById('c%df%d%s%d'));
    markers.setType(%s, %s);
    commentChainById[%d].markers = markers;
  });""" % data, here=True)
Пример #55
0
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