Beispiel #1
1
    def getTitle(self, db, html=False):
        if html:
            title = "<b>%s</b>" % htmlutils.htmlify(self.getName())
        else:
            title = self.getName()

        if not self.isSystemExtension():
            author = self.getAuthor(db)

            try:
                manifest = self.getManifest()
            except ManifestError:
                # Can't access information from the manifest, so assume "yes".
                is_author = True
            else:
                is_author = manifest.isAuthor(db, author)

            if is_author:
                title += " by "
            else:
                title += " hosted by "

            if html:
                title += htmlutils.htmlify(author.fullname)
            else:
                title += author.fullname

        return title
Beispiel #2
0
    def getTitle(self, db, html=False):
        if html:
            title = "<b>%s</b>" % htmlutils.htmlify(self.getName())
        else:
            title = self.getName()

        if not self.isSystemExtension():
            author = self.getAuthor(db)

            try:
                manifest = self.getManifest()
            except ManifestError:
                # Can't access information from the manifest, so assume "yes".
                is_author = True
            else:
                is_author = manifest.isAuthor(db, author)

            if is_author:
                title += " by "
            else:
                title += " hosted by "

            if html:
                title += htmlutils.htmlify(author.fullname)
            else:
                title += author.fullname

        return title
Beispiel #3
0
    def __call__(self, source, output_file, contexts_path):
        self.output = output_file

        blocks = re.split("^((?:<<<<<<<|>>>>>>>)[^\n]*\n)", source, flags=re.MULTILINE)

        in_conflict = False

        for index, block in enumerate(blocks):
            if (index & 1) == 0:
                if in_conflict:
                    blocks = re.split("^(=======[^\n]*\n)", block, flags=re.MULTILINE)
                else:
                    blocks = [block]

                for index, block in enumerate(blocks):
                    if (index & 1) == 0:
                        if block:
                            for token, value in self.lexer.get_tokens(block):
                                self.highlightToken(token, value)
                    else:
                        assert block[0] == "="
                        self.output.write(htmlutils.htmlify(block))
            else:
                assert block[0] == "<" or block[0] == ">"
                self.output.write(htmlutils.htmlify(block))
                in_conflict = block[0] == "<"
Beispiel #4
0
    def __call__(self, source, output_file, contexts_path):
        self.output = output_file

        blocks = re.split("^((?:<<<<<<<|>>>>>>>)[^\n]*\n)", source, flags=re.MULTILINE)

        in_conflict = False

        for index, block in enumerate(blocks):
            if (index & 1) == 0:
                if in_conflict:
                    blocks = re.split("^(=======[^\n]*\n)", block, flags=re.MULTILINE)
                else:
                    blocks = [block]

                for index, block in enumerate(blocks):
                    if (index & 1) == 0:
                        if block:
                            for token, value in self.lexer.get_tokens(block):
                                self.highlightToken(token, value)
                    else:
                        assert block[0] == "="
                        self.output.write(htmlutils.htmlify(block))
            else:
                assert block[0] == "<" or block[0] == ">"
                self.output.write(htmlutils.htmlify(block))
                in_conflict = block[0] == "<"
Beispiel #5
0
 def leader(self, max_length=80, text=False):
     if self.__leader is None: self.__leader = self.comments[0].comment.split("\n", 1)[0]
     if len(self.__leader) > max_length:
         if text: return self.__leader[:max_length - 5] + "[...]"
         else: return htmlutils.htmlify(self.__leader[:max_length - 3]) + "[&#8230;]"
     else:
         if text: return self.__leader
         else: return htmlutils.htmlify(self.__leader)
Beispiel #6
0
    def process(self, db, user, branch_id, new_remote_name=None):
        cursor = db.cursor()

        if not user.hasRole(db, "administrator"):
            cursor.execute("""SELECT 1
                                FROM trackedbranchusers
                               WHERE branch=%s
                                 AND uid=%s""",
                           (branch_id, user.id))

            if not cursor.fetchone():
                raise OperationFailure(code="notallowed",
                                       title="Not allowed!",
                                       message="Operation not permitted.")

        review_state = getTrackedBranchReviewState(db, branch_id)
        if review_state is not None and review_state != "open":
            raise OperationFailure(code="reviewnotopen",
                                   title="The review is not open!",
                                   message="You need to reopen the review before new commits can be added to it.")

        if new_remote_name is not None:
            cursor.execute("""SELECT remote
                                FROM trackedbranches
                               WHERE id=%s""",
                           (branch_id,))

            remote = cursor.fetchone()[0]

            if not gitutils.Repository.lsremote(remote, pattern="refs/heads/" + new_remote_name):
                raise OperationFailure(
                    code="refnotfound",
                    title="Remote ref not found!",
                    message=("Could not find the ref <code>%s</code> in the repository <code>%s</code>."
                             % (htmlutils.htmlify("refs/heads/" + new_remote_name),
                                htmlutils.htmlify(remote))),
                    is_html=True)

            cursor.execute("""UPDATE trackedbranches
                                 SET remote_name=%s,
                                     disabled=FALSE,
                                     next=NULL
                               WHERE id=%s""",
                           (new_remote_name, branch_id))
        else:
            cursor.execute("""UPDATE trackedbranches
                                 SET disabled=FALSE,
                                     next=NULL
                               WHERE id=%s""",
                           (branch_id,))

        db.commit()

        pid = int(open(configuration.services.BRANCHTRACKER["pidfile_path"]).read().strip())
        os.kill(pid, signal.SIGHUP)

        return OperationResult()
Beispiel #7
0
 def leader(self, max_length=80, text=False):
     if self.__leader is None:
         self.__leader = self.comments[0].comment.split("\n", 1)[0]
     if len(self.__leader) > max_length:
         if text: return self.__leader[:max_length - 5] + "[...]"
         else:
             return htmlutils.htmlify(
                 self.__leader[:max_length - 3]) + "[&#8230;]"
     else:
         if text: return self.__leader
         else: return htmlutils.htmlify(self.__leader)
Beispiel #8
0
    def eliminateCommonPrefixes(files, text=False, getpath=None, setpath=None):
        assert (getpath is None) == (setpath is None)

        if getpath is None:

            def defaultGetPath(x):
                return x

            getpath = defaultGetPath
        if setpath is None:

            def defaultSetPath(x, p):
                files[index] = p

            setpath = defaultSetPath

        def commonPrefixLength(pathA, pathB):
            componentsA = pathA.split('/')
            componentsB = pathB.split('/')
            for index in range(min(len(componentsA), len(componentsB))):
                if componentsA[index] != componentsB[index]:
                    return sum(map(len, componentsA[:index])) + index

        if files:
            previous = None
            for index in range(len(files)):
                current = getpath(files[index])

                if index > 0:
                    length = commonPrefixLength(previous, current)
                else:
                    length = 0

                if text:
                    if length > 4:
                        updated = (" " * (length - 4) + ".../" +
                                   textutils.escape(current[length:]))
                    else:
                        updated = textutils.escape(current)
                else:
                    if length > 2:
                        updated = (" " * (length - 2) + "&#8230;/" +
                                   htmlutils.htmlify(
                                       textutils.escape(current[length:])))
                    else:
                        updated = htmlutils.htmlify(textutils.escape(current))

                if updated != current:
                    setpath(files[index], updated)

                previous = current

        return files
Beispiel #9
0
 def highlightToken(self, token):
     if token.iskeyword():
         self.output.write("<b class='kw'>" + str(token) + "</b>")
     elif token.isidentifier():
         self.output.write("<b class='id'>" + str(token) + "</b>")
     elif token.iscomment():
         if str(token)[0:2] == "/*":
             lines = str(token).splitlines()
             self.output.write("\n".join(["<b class='com'>" + htmlutils.htmlify(line) + "</b>" for line in lines]))
         else:
             self.output.write("<b class='com'>" + htmlutils.htmlify(token) + "</b>")
     elif token.isppdirective():
         lines = str(token).split("\n")
         self.output.write("\n".join(["<b class='pp'>" + htmlutils.htmlify(line) + "</b>" for line in lines]))
     elif token.isspace():
         self.output.write(str(token))
     elif token.isconflictmarker():
         self.output.write(htmlutils.htmlify(token))
     elif str(token)[0] == '"':
         self.output.write("<b class='str'>" + htmlutils.htmlify(token) + "</b>")
     elif str(token)[0] == "'":
         self.output.write("<b class='ch'>" + htmlutils.htmlify(token) + "</b>")
     elif token.isfloat():
         self.output.write("<b class='fp'>" + str(token) + "</b>")
     elif token.isint():
         self.output.write("<b class='int'>" + str(token) + "</b>")
     elif token.isbyteordermark():
         self.output.write(htmlutils.htmlify(u"\ufeff"))
     else:
         self.output.write("<b class='op'>" + htmlutils.htmlify(token) + "</b>")
Beispiel #10
0
    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()
Beispiel #11
0
    def highlightToken(self, token, value):
        def tag(cls, value): return "<b class='%s'>%s</b>" % (cls, htmlutils.htmlify(value))
        def tagm(cls, value):
            if value == "\n": return value
            else:
                res = []
                for line in value.splitlines():
                    if line: res.append(tag(cls, line))
                    else: res.append(line)
                if value.endswith("\n"): res.append("")
                return "\n".join(res)

        value = value.encode("utf-8")

        if token in pygments.token.Token.Punctuation or token in pygments.token.Token.Operator:
            self.output.write(tag("op", value))
        elif token in pygments.token.Token.Name or token in pygments.token.Token.String.Symbol:
            self.output.write(tag("id", value))
        elif token in pygments.token.Token.Keyword:
            self.output.write(tag("kw", value))
        elif token in pygments.token.Token.String:
            self.output.write(tagm("str", value))
        elif token in pygments.token.Token.Comment:
            self.output.write(tagm("com", value))
        elif token in pygments.token.Token.Number.Integer:
            self.output.write(tag("int", value))
        elif token in pygments.token.Token.Number.Float:
            self.output.write(tag("fp", value))
        else:
            self.output.write(htmlutils.htmlify(value))
Beispiel #12
0
 def execute(self, db, req):
     from htmlutils import htmlify
     if not req.allowRedirect(self.status):
         self.status = 403
         self.body = [
             "Cowardly refusing to redirect %s request." % req.method
         ]
     else:
         req.addResponseHeader("Location", self.location)
         self.body = [
             "<p>Please go here: <a href=%s>%s</a>." %
             (htmlify(self.location,
                      attributeValue=True), htmlify(self.location))
         ]
         self.content_type = "text/html"
     return super(Redirect, self).execute(db, req)
Beispiel #13
0
    def loadNewLines(self, highlighted=False, request_highlight=False):
        """Load the lines of the new version of the file, optionally highlighted."""

        from diff.parse import splitlines

        if self.new_sha1 is None or self.new_sha1 == '0' * 40:
            self.new_plain = []
            self.new_highlighted = []
            return
        elif self.new_mode and self.new_mode == "160000":
            self.new_plain = self.new_highlighted = ["Subproject commit %s" % self.new_sha1]
            return

        if highlighted:
            if self.new_highlighted and self.new_is_highlighted: return
            else:
                self.new_is_highlighted = True
                language = self.getLanguage(use_content="new")
                if language:
                    data = syntaxhighlight.readHighlight(self.repository, self.new_sha1, self.path, language, request=request_highlight)
                elif self.new_highlighted: return
                else:
                    data = htmlutils.htmlify(textutils.decode(self.repository.fetch(self.new_sha1).data))
                self.new_highlighted = splitlines(data)
                self.new_eof_eol = data and data[-1] in "\n\r"
        else:
            if self.new_plain: return
            else:
                data = self.repository.fetch(self.new_sha1).data
                self.new_plain = splitlines(data)
                self.new_eof_eol = data and data[-1] in "\n\r"
Beispiel #14
0
    def highlightToken(self, token, value):
        def tag(cls, value): return "<b class='%s'>%s</b>" % (cls, htmlutils.htmlify(value))
        def tagm(cls, value):
            if value == "\n": return value
            else:
                res = []
                for line in value.splitlines():
                    if line: res.append(tag(cls, line))
                    else: res.append(line)
                if value.endswith("\n"): res.append("")
                return "\n".join(res)

        value = value.encode("utf-8")

        if token in pygments.token.Token.Punctuation or token in pygments.token.Token.Operator:
            self.output.write(tag("op", value))
        elif token in pygments.token.Token.Name or token in pygments.token.Token.String.Symbol:
            self.output.write(tag("id", value))
        elif token in pygments.token.Token.Keyword:
            self.output.write(tag("kw", value))
        elif token in pygments.token.Token.String:
            self.output.write(tagm("str", value))
        elif token in pygments.token.Token.Comment:
            self.output.write(tagm("com", value))
        elif token in pygments.token.Token.Number.Integer:
            self.output.write(tag("int", value))
        elif token in pygments.token.Token.Number.Float:
            self.output.write(tag("fp", value))
        else:
            self.output.write(htmlutils.htmlify(value))
Beispiel #15
0
    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()
Beispiel #16
0
def handleException(db, req, user, as_html=False):
    error_message = traceback.format_exc()
    environ = req.getEnvironment()

    environ["wsgi.errors"].write(error_message)

    if not user or not db or not user.hasRole(db, "developer"):
        url = wsgiref.util.request_uri(environ)

        x_forwarded_host = req.getRequestHeader("X-Forwarded-Host")
        if x_forwarded_host:
            original_host = x_forwarded_host.split(",")[0].strip()
            def replace_host(match):
                return match.group(1) + original_host
            url = re.sub("^([a-z]+://)[^/]+", replace_host, url)

        if user and not user.isAnonymous():
            user_string = str(user)
        else:
            user_string = "<anonymous>"

        mailutils.sendExceptionMessage(db,
            "wsgi", "\n".join(["User:   %s" % user_string,
                               "Method: %s" % req.method,
                               "URL:    %s" % url,
                               "",
                               error_message]))

        admin_message_sent = True
    else:
        admin_message_sent = False

    if not user or not db or user.hasRole(db, "developer") \
            or configuration.debug.IS_DEVELOPMENT \
            or configuration.debug.IS_TESTING:
        error_title = "Unexpected error!"
        error_message = error_message.strip()
        if as_html:
            error_message = "<p class='pre inset'>%s</p>" % htmlify(error_message)
        error_body = [error_message]
        if admin_message_sent:
            admin_message_sent = ("A message has been sent to the system "
                                  "administrator(s) with details about the "
                                  "problem.")
            if as_html:
                admin_message_sent = "<p>%s</p>" % admin_message_sent
            error_body.append(admin_message_sent)
    else:
        error_title = "Request failed!"
        error_message = ("An unexpected error occurred while handling the "
                         "request.  A message has been sent to the system "
                         "administrator(s) with details about the problem.  "
                         "Please contact them for further information and/or "
                         "assistance.")
        if as_html:
            error_message = "<p>%s</p>" % error_message
        error_body = [error_message]

    return error_title, error_body
Beispiel #17
0
    def eliminateCommonPrefixes(files, text=False, getpath=None, setpath=None):
        assert (getpath is None) == (setpath is None)

        if getpath is None:
            def defaultGetPath(x): return x
            getpath = defaultGetPath
        if setpath is None:
            def defaultSetPath(x, p): files[index] = p
            setpath = defaultSetPath

        def commonPrefixLength(pathA, pathB):
            componentsA = pathA.split('/')
            componentsB = pathB.split('/')
            for index in range(min(len(componentsA), len(componentsB))):
                if componentsA[index] != componentsB[index]:
                    return sum(map(len, componentsA[:index])) + index

        if files:
            previous = None
            for index in range(len(files)):
                current = getpath(files[index])

                if index > 0:
                    length = commonPrefixLength(previous, current)
                else:
                    length = 0

                if text:
                    if length > 4:
                        updated = (" " * (length - 4) + ".../" +
                                   textutils.escape(current[length:]))
                    else:
                        updated = textutils.escape(current)
                else:
                    if length > 2:
                        updated = (" " * (length - 2) + "&#8230;/" +
                                   htmlutils.htmlify(textutils.escape(current[length:])))
                    else:
                        updated = htmlutils.htmlify(textutils.escape(current))

                if updated != current:
                    setpath(files[index], updated)

                previous = current

        return files
Beispiel #18
0
def summarize(string, max_length=80, as_html=False):
    if len(string) <= max_length:
        return string
    string = string[:max_length - 5]
    if as_html:
        import htmlutils
        return htmlutils.htmlify(string) + "[&#8230;]"
    else:
        return string + "[...]"
Beispiel #19
0
def summarize(string, max_length=80, as_html=False):
    if len(string) <= max_length:
        return string
    string = string[:max_length - 5]
    if as_html:
        import htmlutils
        return htmlutils.htmlify(string) + "[&#8230;]"
    else:
        return string + "[...]"
Beispiel #20
0
            def linkify(match):
                config_item, reference_text, reference_name = match.groups()

                if config_item:
                    url = "/config?highlight=%s" % config_item
                    text = config_item
                    title = None
                else:
                    reference_name = re.sub(r"\s+", " ", reference_name)
                    assert reference_name in references, reference_name
                    url, title = references[reference_name]
                    text = reference_text

                link = "<a href=%s" % htmlutils.htmlify(url, True)

                if title:
                    link += " title=%s" % htmlutils.htmlify(title, True)

                return link + ">%s</a>" % htmlutils.htmlify(text)
Beispiel #21
0
            def linkify(match):
                config_item, reference_text, reference_name = match.groups()

                if config_item:
                    url = "/config?highlight=%s" % config_item
                    text = config_item
                    title = None
                else:
                    reference_name = re.sub(r"\s+", " ", reference_name)
                    assert reference_name in references, reference_name
                    url, title = references[reference_name]
                    text = reference_text

                link = "<a href=%s" % htmlutils.htmlify(url, True)

                if title:
                    link += " title=%s" % htmlutils.htmlify(title, True)

                return link + ">%s</a>" % htmlutils.htmlify(text)
Beispiel #22
0
 def addItem(self, heading, value, description=None, buttons=None):
     row = self.table.tr("item")
     row.td("name").innerHTML(htmlutils.htmlify(heading).replace(" ", "&nbsp;") + ":")
     cell = row.td("value", colspan=2).preformatted()
     if callable(value): value(cell)
     else: cell.text(str(value))
     if buttons:
         div = cell.div("buttons")
         for label, onclick in buttons:
             div.button(onclick=onclick).text(label)
     if description is not None:
         self.table.tr("help").td(colspan=len(self.columns)).text(description)
Beispiel #23
0
 def addItem(self, heading, value, description=None, buttons=None):
     row = self.table.tr("item")
     row.td("name").innerHTML(htmlutils.htmlify(heading).replace(" ", "&nbsp;") + ":")
     cell = row.td("value", colspan=2).preformatted()
     if callable(value): value(cell)
     else: cell.text(str(value))
     if buttons:
         div = cell.div("buttons")
         for label, onclick in buttons:
             div.button(onclick=onclick).text(label)
     if description is not None:
         self.table.tr("help").td(colspan=len(self.columns)).text(description)
Beispiel #24
0
    def process(self, db, user, remote, pattern=None):
        if pattern: regexp = re.compile(pattern.replace("*", ".*"))
        else: regexp = None

        try:
            refs = gitutils.Repository.lsremote(remote, regexp=regexp)
        except gitutils.GitCommandError, error:
            if error.output.splitlines()[0].endswith("does not appear to be a git repository"):
                raise OperationFailure(
                    code="invalidremote",
                    title="Invalid remote!",
                    message=("<code>%s</code> does not appear to be a valid Git repository."
                             % htmlutils.htmlify(remote)),
                    is_html=True)
            else:
                raise
Beispiel #25
0
 def highlightToken(self, token):
     if token.iskeyword():
         self.output.write("<b class='kw'>" + str(token) + "</b>")
     elif token.isidentifier():
         self.output.write("<b class='id'>" + str(token) + "</b>")
     elif token.iscomment():
         if str(token)[0:2] == "/*":
             lines = str(token).splitlines()
             self.output.write("\n".join([
                 "<b class='com'>" + htmlutils.htmlify(line) + "</b>"
                 for line in lines
             ]))
         else:
             self.output.write("<b class='com'>" +
                               htmlutils.htmlify(token) + "</b>")
     elif token.isppdirective():
         lines = str(token).split("\n")
         self.output.write("\n".join([
             "<b class='pp'>" + htmlutils.htmlify(line) + "</b>"
             for line in lines
         ]))
     elif token.isspace():
         self.output.write(str(token))
     elif token.isconflictmarker():
         self.output.write(htmlutils.htmlify(token))
     elif str(token)[0] == '"':
         self.output.write("<b class='str'>" + htmlutils.htmlify(token) +
                           "</b>")
     elif str(token)[0] == "'":
         self.output.write("<b class='ch'>" + htmlutils.htmlify(token) +
                           "</b>")
     elif token.isfloat():
         self.output.write("<b class='fp'>" + str(token) + "</b>")
     elif token.isint():
         self.output.write("<b class='int'>" + str(token) + "</b>")
     elif token.isbyteordermark():
         self.output.write(htmlutils.htmlify(u"\ufeff"))
     else:
         self.output.write("<b class='op'>" + htmlutils.htmlify(token) +
                           "</b>")
Beispiel #26
0
def readHighlight(repository, sha1, path, language, request=False):
    path = generateHighlightPath(sha1, language)

    if os.path.isfile(path):
        os.utime(path, None)
        source = open(path).read()
    elif os.path.isfile(path + ".bz2"):
        os.utime(path + ".bz2", None)
        source = bz2.BZ2File(path + ".bz2", "r").read()
    elif request:
        import request
        request.requestHighlights(repository, { sha1: (path, language) })
        return readHighlight(repository, sha1, path, language)
    else:
        source = None

    if not source:
        source = htmlutils.htmlify(repository.fetch(sha1)[2])

    return source.replace("\r", "")
Beispiel #27
0
def readHighlight(repository, sha1, path, language, request=False):
    path = generateHighlightPath(sha1, language)

    if os.path.isfile(path):
        os.utime(path, None)
        source = open(path).read()
    elif os.path.isfile(path + ".bz2"):
        os.utime(path + ".bz2", None)
        source = bz2.BZ2File(path + ".bz2", "r").read()
    elif request:
        import request
        request.requestHighlights(repository, {sha1: (path, language)})
        return readHighlight(repository, sha1, path, language)
    else:
        source = None

    if not source:
        source = htmlutils.htmlify(repository.fetch(sha1)[2])

    return source
Beispiel #28
0
    def loadNewLines(self, highlighted=False, request_highlight=False):
        """Load the lines of the new version of the file, optionally highlighted."""

        from diff.parse import splitlines

        if self.new_sha1 is None or self.new_sha1 == '0' * 40:
            self.new_plain = []
            self.new_highlighted = []
            return
        elif self.new_mode and self.new_mode == "160000":
            self.new_plain = self.new_highlighted = [
                "Subproject commit %s" % self.new_sha1
            ]
            return

        if highlighted:
            if self.new_highlighted and self.new_is_highlighted: return
            else:
                self.new_is_highlighted = True
                language = self.getLanguage(use_content="new")
                if language:
                    data = syntaxhighlight.readHighlight(
                        self.repository,
                        self.new_sha1,
                        self.path,
                        language,
                        request=request_highlight)
                elif self.new_highlighted:
                    return
                else:
                    data = htmlutils.htmlify(
                        textutils.decode(
                            self.repository.fetch(self.new_sha1).data))
                self.new_highlighted = splitlines(data)
                self.new_eof_eol = data and data[-1] in "\n\r"
        else:
            if self.new_plain: return
            else:
                data = self.repository.fetch(self.new_sha1).data
                self.new_plain = splitlines(data)
                self.new_eof_eol = data and data[-1] in "\n\r"
Beispiel #29
0
    def process(self, db, user, repository_name, remote, branch, upstream="refs/heads/master"):
        repository = gitutils.Repository.fromName(db, repository_name)

        cursor = db.cursor()

        # Check if any other repository is currently tracking branches from this
        # remote.  If that's the case, then the user most likely either selected
        # the wrong repository or entered the wrong remote.
        cursor.execute("""SELECT repositories.name
                            FROM repositories
                            JOIN trackedbranches ON (trackedbranches.repository=repositories.id)
                           WHERE repositories.id!=%s
                             AND trackedbranches.remote=%s""",
                       (repository.id, remote))
        for (other_name,) in cursor:
            raise OperationFailure(
                code="badremote",
                title="Bad remote!",
                message=("The remote <code>%s</code> appears to be related to "
                         "another repository on this server (<code>%s</code>).  "
                         "You most likely shouldn't be importing branches from "
                         "it into the selected repository (<code>%s</code>)."
                         % (htmlutils.htmlify(remote), htmlutils.htmlify(other_name),
                            htmlutils.htmlify(repository_name))),
                is_html=True)

        if not branch.startswith("refs/"):
            branch = "refs/heads/%s" % branch

        try:
            head_sha1 = repository.fetchTemporaryFromRemote(remote, branch)
        except gitutils.GitReferenceError:
            raise OperationFailure(
                code="refnotfound",
                title="Remote ref not found!",
                message=("Could not find the ref <code>%s</code> in the repository <code>%s</code>."
                         % (htmlutils.htmlify(branch), htmlutils.htmlify(remote))),
                is_html=True)
        except gitutils.GitCommandError, error:
            if error.output.splitlines()[0].endswith("does not appear to be a git repository"):
                raise OperationFailure(
                    code="invalidremote",
                    title="Invalid remote!",
                    message=("<code>%s</code> does not appear to be a valid Git repository."
                             % htmlutils.htmlify(remote)),
                    is_html=True)
            else:
                raise
Beispiel #30
0
    def process(self, db, user, remote, pattern=None):
        if pattern: regexp = re.compile(pattern.replace("*", ".*"))
        else: regexp = None

        try:
            refs = gitutils.Repository.lsremote(remote, regexp=regexp)
        except gitutils.GitCommandError as error:
            if error.output.splitlines()[0].endswith(
                    "does not appear to be a git repository"):
                raise OperationFailure(
                    code="invalidremote",
                    title="Invalid remote!",
                    message=
                    ("<code>%s</code> does not appear to be a valid Git repository."
                     % htmlutils.htmlify(remote)),
                    is_html=True)
            else:
                raise
        else:
            branches = dict([(ref[1], ref[0]) for ref in refs])
            return OperationResult(branches=branches)
Beispiel #31
0
    def next(self):
        if self.failed:
            raise StopIteration

        try:
            if self.first:
                value = self.first
                self.first = None
            else:
                value = self.result.next()

            self.db.rollback()
            return value
        except StopIteration:
            self.db.close()
            raise
        except Exception:
            error_title, error_body = handleException(
                self.db, self.req, self.user)

            self.db.close()

            if self.req.getContentType().startswith("text/html"):
                self.failed = True

                error_body = "".join("<p>%s</p>" % htmlify(part)
                                     for part in error_body)

                # Close a bunch of tables, in case we're in any.  Not
                # pretty, but probably makes the end result prettier.
                return ("</table></table></table></table></div>"
                        "<div class='fatal'><table align=center><tr>"
                        "<td><h1>%s</h1>%s</td></tr></table></div>"
                        % (error_title, error_body))
            else:
                raise StopIteration
Beispiel #32
0
def main(environ, start_response):
    request_start = time.time()

    db = dbutils.Database()
    user = None

    try:
        try:
            req = request.Request(db, environ, start_response)

            if req.user is None:
                if configuration.base.AUTHENTICATION_MODE == "critic":
                    if configuration.base.SESSION_TYPE == "httpauth":
                        req.setStatus(401)
                        req.addResponseHeader("WWW-Authenticate",
                                              "Basic realm=\"Critic\"")
                        req.start()
                        return
                    elif configuration.base.ALLOW_ANONYMOUS_USER or req.path in (
                            "login", "validatelogin"):
                        user = dbutils.User.makeAnonymous()
                    elif req.method == "GET":
                        raise page.utils.NeedLogin, req
                    else:
                        # Don't try to redirect POST requests to the login page.
                        req.setStatus(403)
                        req.start()
                        return
            else:
                try:
                    user = dbutils.User.fromName(db, req.user)
                except dbutils.NoSuchUser:
                    cursor = db.cursor()
                    cursor.execute(
                        """INSERT INTO users (name, email, fullname)
                                           VALUES (%s, %s, %s)
                                        RETURNING id""",
                        (req.user, getUserEmailAddress(req.user), req.user))
                    user = dbutils.User.fromId(db, cursor.fetchone()[0])
                    db.commit()

            user.loadPreferences(db)

            if user.status == 'retired':
                cursor = db.cursor()
                cursor.execute("UPDATE users SET status='current' WHERE id=%s",
                               (user.id, ))
                user = dbutils.User.fromId(db, user.id)
                db.commit()

            if not user.getPreference(db, "debug.profiling.databaseQueries"):
                db.disableProfiling()

            if not req.path:
                if user.isAnonymous():
                    location = "tutorial"
                else:
                    location = user.getPreference(db, "defaultPage")

                if req.query:
                    location += "?" + req.query

                req.setStatus(307)
                req.addResponseHeader("Location", location)
                req.start()
                return

            if req.path == "redirect":
                target = req.getParameter("target", "/")

                if req.method == "POST":
                    # Don't use HTTP redirect for POST requests.

                    req.setContentType("text/html")
                    req.start()

                    yield "<meta http-equiv='refresh' content='0; %s'>" % htmlify(
                        target)
                    return
                else:
                    raise page.utils.MovedTemporarily, target

            if req.path.startswith("!/"):
                req.path = req.path[2:]
            elif configuration.extensions.ENABLED:
                handled = extensions.executePage(db, req, user)
                if handled:
                    req.start()
                    yield handled
                    return

            if req.path.startswith("r/"):
                req.query = "id=" + req.path[2:] + ("&" + req.query
                                                    if req.query else "")
                req.path = "showreview"

            if configuration.extensions.ENABLED:
                match = RE_EXTENSION_RESOURCE.match(req.path)
                if match:
                    content_type, resource = extensions.getExtensionResource(
                        req, db, user, match.group(1))
                    if resource:
                        req.setContentType(content_type)
                        req.start()
                        yield resource
                        return
                    else:
                        req.setStatus(404)
                        req.start()
                        return

            if req.path.startswith("download/"): operation = download
            else: operation = operations.get(req.path)
            if operation:
                req.setContentType("text/plain")

                try:
                    result = operation(req, db, user)
                except OperationError, error:
                    result = error
                except page.utils.DisplayMessage, message:
                    result = "error:" + message.title
                    if message.body: result += "  " + message.body
                except Exception, exception:
                    result = "error:\n" + "".join(
                        traceback.format_exception(*sys.exc_info()))
Beispiel #33
0
    def process(self, db, user, filter_type, path, delegates, repository_id=None, repository_name=None, replaced_filter_id=None):
        path = reviewing.filters.sanitizePath(path)

        if "*" in path:
            try:
                reviewing.filters.validatePattern(path)
            except reviewing.filters.PatternError as error:
                raise OperationFailure(code="invalidpattern",
                                       title="Invalid path pattern",
                                       message="There are invalid wild-cards in the path: %s" % error.message)

        if filter_type == "reviewer":
            delegates = filter(None, delegates)
            invalid_delegates = []
            for delegate in delegates:
                try:
                    dbutils.User.fromName(db, delegate)
                except dbutils.NoSuchUser:
                    invalid_delegates.append(delegate)
            if invalid_delegates:
                raise OperationFailure(code="invaliduser",
                                       title="Invalid delegate(s)",
                                       message="These user-names are not valid: %s" % ", ".join(invalid_delegates))
        else:
            delegates = []

        cursor = db.cursor()

        if repository_id is None:
            cursor.execute("""SELECT id
                                FROM repositories
                               WHERE name=%s""",
                           (repository_name,))
            repository_id = cursor.fetchone()[0]

        if replaced_filter_id is not None:
            cursor.execute("""SELECT 1
                                FROM filters
                               WHERE id=%s
                                 AND uid=%s""",
                           (replaced_filter_id, user.id))

            if not cursor.fetchone():
                raise OperationFailure(code="invalidoperation",
                                       title="Invalid operation",
                                       message="Filter to replace does not exist or belongs to another user!")

            cursor.execute("""DELETE
                                FROM filters
                               WHERE id=%s""",
                           (replaced_filter_id,))

        cursor.execute("""SELECT 1
                            FROM filters
                           WHERE uid=%s
                             AND repository=%s
                             AND path=%s""",
                       (user.id, repository_id, path))

        if cursor.fetchone():
            raise OperationFailure(code="duplicatefilter",
                                   title="Duplicate filter",
                                   message=("You already have a filter for the path <code>%s</code> in this repository."
                                            % htmlutils.htmlify(path)),
                                   is_html=True)

        cursor.execute("""INSERT INTO filters (uid, repository, path, type, delegate)
                               VALUES (%s, %s, %s, %s, %s)
                            RETURNING id""",
                       (user.id, repository_id, path, filter_type, ",".join(delegates)))

        filter_id = cursor.fetchone()[0]

        db.commit()

        return OperationResult(filter_id=filter_id)
Beispiel #34
0
def renderShowBatch(req, db, user):
    batch_id = page.utils.getParameter(req, "batch", None, filter=int)
    review_id = page.utils.getParameter(req, "review", None, filter=int)

    cursor = db.cursor()

    if batch_id is None and review_id is None:
        return page.utils.displayMessage(db, req, user, "Missing argument: 'batch'")

    if batch_id:
        cursor.execute("SELECT review, uid, comment FROM batches WHERE id=%s", (batch_id,))

        row = cursor.fetchone()
        if not row:
            raise page.utils.DisplayMessage("Invalid batch ID: %d" % batch_id)

        review_id, author_id, chain_id = row
        author = dbutils.User.fromId(db, author_id)
    else:
        chain_id = None
        author = user

    review = dbutils.Review.fromId(db, review_id)

    if chain_id:
        batch_chain = review_comment.CommentChain.fromId(db, chain_id, user, review=review)
        batch_chain.loadComments(db, user)
    else:
        batch_chain = None

    document = htmlutils.Document(req)

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

    page.utils.generateHeader(body, db, user, lambda target: review_utils.renderDraftItems(db, user, review, target), extra_links=[("r/%d" % review.id, "Back to Review")])

    document.addExternalStylesheet("resource/showreview.css")
    document.addExternalStylesheet("resource/showbatch.css")
    document.addExternalStylesheet("resource/review.css")
    document.addExternalStylesheet("resource/comment.css")
    document.addExternalScript("resource/review.js")
    document.addExternalScript("resource/comment.js")
    document.addInternalScript(user.getJS())
    document.addInternalScript(review.getJS())

    if batch_chain:
        document.addInternalScript("commentChainById[%d] = %s;" % (batch_chain.id, batch_chain.getJSConstructor()))

    target = body.div("main")

    table = target.table('paleyellow basic comments', align='center')
    table.col(width='10%')
    table.col(width='80%')
    table.col(width='10%')
    table.tr().td('h1', colspan=3).h1().text("Review by %s" % htmlify(author.fullname))

    if batch_chain:
        batch_chain.loadComments(db, user)

        row = table.tr("line")
        row.td("heading").text("Comment:")
        row.td("value").preformatted().div("text").text(htmlify(batch_chain.comments[0].comment))
        row.td("status").text()

    def renderFiles(title, cursor):
        files = []

        for file_id, delete_count, insert_count in cursor.fetchall():
            files.append((dbutils.describe_file(db, file_id), delete_count, insert_count))

        paths = []
        deleted = []
        inserted = []

        for path, delete_count, insert_count in sorted(files):
            paths.append(path)
            deleted.append(delete_count)
            inserted.append(insert_count)

        if paths:
            diff.File.eliminateCommonPrefixes(paths)

            row = table.tr("line")
            row.td("heading").text(title)

            files_table = row.td().table("files callout")
            headers = files_table.thead().tr()
            headers.th("path").text("Changed Files")
            headers.th("lines", colspan=2).text("Lines")

            files = files_table.tbody()
            for path, delete_count, insert_count in zip(paths, deleted, inserted):
                file = files.tr()
                file.td("path").preformatted().innerHTML(path)
                file.td("lines").preformatted().text("-%d" % delete_count if delete_count else None)
                file.td("lines").preformatted().text("+%d" % insert_count if insert_count else None)

            row.td("status").text()

    def condition(table_name):
        if batch_id:
            return "%s.batch=%d" % (table_name, batch_id)
        else:
            return "review=%d AND %s.batch IS NULL AND %s.uid=%d" % (review.id, table_name, table_name, author.id)

    cursor.execute("""SELECT reviewfiles.file, SUM(deleted), SUM(inserted)
                        FROM reviewfiles
                        JOIN reviewfilechanges ON (reviewfilechanges.file=reviewfiles.id)
                       WHERE %s
                         AND reviewfilechanges.to_state='reviewed'
                    GROUP BY reviewfiles.file""" % condition("reviewfilechanges"))
    renderFiles("Reviewed:", cursor)

    cursor.execute("""SELECT reviewfiles.file, SUM(deleted), SUM(inserted)
                        FROM reviewfiles
                        JOIN reviewfilechanges ON (reviewfilechanges.file=reviewfiles.id)
                       WHERE %s
                         AND reviewfilechanges.to_state='pending'
                    GROUP BY reviewfiles.file""" % condition("reviewfilechanges"))
    renderFiles("Unreviewed:", cursor)

    def renderChains(title, cursor, replies):
        all_chains = [review_comment.CommentChain.fromId(db, chain_id, user, review=review)
                      for (chain_id,) in cursor]

        if not all_chains:
            return

        for chain in all_chains:
            chain.loadComments(db, user)

        issue_chains = filter(lambda chain: chain.type == "issue", all_chains)
        draft_issues = filter(lambda chain: chain.state == "draft", issue_chains)
        open_issues = filter(lambda chain: chain.state == "open", issue_chains)
        addressed_issues = filter(lambda chain: chain.state == "addressed", issue_chains)
        closed_issues = filter(lambda chain: chain.state == "closed", issue_chains)
        note_chains = filter(lambda chain: chain.type == "note", all_chains)
        draft_notes = filter(lambda chain: chain.state == "draft" and chain != batch_chain, note_chains)
        open_notes = filter(lambda chain: chain.state == "open" and chain != batch_chain, note_chains)

        def renderChains(target, chains):
            for chain in chains:
                row = target.tr("comment")
                row.td("author").text(chain.user.fullname)
                row.td("title").a(href="showcomment?chain=%d" % chain.id).innerHTML(chain.leader())
                row.td("when").text(chain.when())

        def showcomments(filter_param):
            params = { "review": review.id, "filter": filter_param }
            if batch_id:
                params["batch"] = batch_id
            return htmlutils.URL("/showcomments", **params)

        if draft_issues or open_issues or addressed_issues or closed_issues:
            h2 = table.tr().td("h2", colspan=3).h2().text(title)
            if len(draft_issues) + len(open_issues) + len(addressed_issues) + len(closed_issues) > 1:
                h2.a(href=showcomments("issues")).text("[display all]")

            if draft_issues:
                h3 = table.tr(id="draft-issues").td("h3", colspan=3).h3().text("Draft issues")
                if len(draft_issues) > 1:
                    h3.a(href=showcomments("draft-issues")).text("[display all]")
                renderChains(table, draft_issues)

            if batch_id is not None or replies:
                if open_issues:
                    h3 = table.tr(id="open-issues").td("h3", colspan=3).h3().text("Still open issues")
                    if batch_id and len(open_issues) > 1:
                        h3.a(href=showcomments("open-issues")).text("[display all]")
                    renderChains(table, open_issues)

                if addressed_issues:
                    h3 = table.tr(id="addressed-issues").td("h3", colspan=3).h3().text("Now addressed issues")
                    if batch_id and len(addressed_issues) > 1:
                        h3.a(href=showcomments("addressed-issues")).text("[display all]")
                    renderChains(table, addressed_issues)

                if closed_issues:
                    h3 = table.tr(id="closed-issues").td("h3", colspan=3).h3().text("Now closed issues")
                    if batch_id and len(closed_issues) > 1:
                        h3.a(href=showcomments("closed-issues")).text("[display all]")
                    renderChains(table, closed_issues)

        if draft_notes or open_notes:
            h2 = table.tr().td("h2", colspan=3).h2().text(title)
            if len(draft_notes) + len(open_notes) > 1:
                h2.a(href=showcomments("notes")).text("[display all]")

            if draft_notes:
                h3 = table.tr(id="draft-notes").td("h3", colspan=3).h3().text("Draft notes")
                if len(draft_notes) > 1:
                    h3.a(href=showcomments("draft-notes")).text("[display all]")
                renderChains(table, draft_notes)

            if open_notes:
                h3 = table.tr(id="notes").td("h3", colspan=3).h3().text("Notes")
                if batch_id and len(open_notes) > 1:
                    h3.a(href=showcomments("open-notes")).text("[display all]")
                renderChains(table, open_notes)

    cursor.execute("SELECT id FROM commentchains WHERE %s AND type='issue'" % condition("commentchains"))

    renderChains("Raised issues", cursor, False)

    cursor.execute("""SELECT commentchains.id
                        FROM commentchains
                        JOIN commentchainchanges ON (commentchainchanges.chain=commentchains.id)
                       WHERE %s
                         AND to_state='closed'""" % condition("commentchainchanges"))

    renderChains("Resolved issues", cursor, False)

    cursor.execute("""SELECT commentchains.id
                        FROM commentchains
                        JOIN commentchainchanges ON (commentchainchanges.chain=commentchains.id)
                       WHERE %s
                         AND to_state='open'""" % condition("commentchainchanges"))

    renderChains("Reopened issues", cursor, False)

    cursor.execute("""SELECT commentchains.id
                        FROM commentchains
                        JOIN commentchainchanges ON (commentchainchanges.chain=commentchains.id)
                       WHERE %s
                         AND to_type='issue'""" % condition("commentchainchanges"))

    renderChains("Converted into issues", cursor, False)

    cursor.execute("""SELECT commentchains.id
                        FROM commentchains
                        JOIN commentchainchanges ON (commentchainchanges.chain=commentchains.id)
                       WHERE %s
                         AND to_type='note'""" % condition("commentchainchanges"))

    renderChains("Converted into notes", cursor, False)

    cursor.execute("SELECT id FROM commentchains WHERE %s AND type='note'" % condition("commentchains"))

    renderChains("Written notes", cursor, False)

    cursor.execute("""SELECT commentchains.id
                        FROM commentchains
                        JOIN comments ON (comments.chain=commentchains.id)
                       WHERE %s
                         AND comments.id!=commentchains.first_comment""" % condition("comments"))

    renderChains("Replied to", cursor, True)

    return document
Beispiel #35
0
def createReview(db, user, repository, commits, branch_name, summary, description, from_branch_name=None, via_push=False, reviewfilters=None, applyfilters=True, applyparentfilters=False, recipientfilters=None):
    cursor = db.cursor()

    if via_push:
        applyparentfilters = bool(user.getPreference(db, 'review.applyUpstreamFilters'))

    branch = dbutils.Branch.fromName(db, repository, branch_name)

    if branch is not None:
        raise OperationFailure(
            code="branchexists",
            title="Invalid review branch name",
            message="""\
<p>There is already a branch named <code>%s</code> in the repository.  You have
to select a different name.</p>

<p>If you believe the existing branch was created during an earlier (failed)
attempt to create this review, you can try to delete it from the repository
using the command<p>

<pre>  git push &lt;remote&gt; :%s</pre>

<p>and then press the "Submit Review" button on this page again."""
            % (htmlutils.htmlify(branch_name), htmlutils.htmlify(branch_name)),
            is_html=True)

    if not commits:
        raise OperationFailure(
            code="nocommits",
            title="No commits specified",
            message="You need at least one commit to create a review.")

    commitset = log_commitset.CommitSet(commits)
    heads = commitset.getHeads()

    if len(heads) != 1:
        # There is really no plausible way for this error to occur.
        raise OperationFailure(
            code="disconnectedtree",
            title="Disconnected tree",
            message=("The specified commits do do not form a single connected "
                     "tree.  Creating a review of them is not supported."))

    head = heads.pop()

    if len(commitset.getTails()) != 1:
        tail_id = None
    else:
        tail_id = gitutils.Commit.fromSHA1(db, repository, commitset.getTails().pop()).getId(db)

    if not via_push:
        try:
            repository.createBranch(branch_name, head.sha1)
        except gitutils.GitCommandError as error:
            raise OperationFailure(
                code="branchfailed",
                title="Failed to create review branch",
                message=("<p><b>Output from git:</b></p>"
                         "<code style='padding-left: 1em'>%s</code>"
                         % htmlutils.htmlify(error.output)),
                is_html=True)

    try:
        cursor.execute("INSERT INTO branches (repository, name, head, tail, type) VALUES (%s, %s, %s, %s, 'review') RETURNING id", [repository.id, branch_name, head.getId(db), tail_id])

        branch_id = cursor.fetchone()[0]
        reachable_values = [(branch_id, commit.getId(db)) for commit in commits]

        cursor.executemany("INSERT INTO reachable (branch, commit) VALUES (%s, %s)", reachable_values)

        cursor.execute("INSERT INTO reviews (type, branch, state, summary, description, applyfilters, applyparentfilters) VALUES ('official', %s, 'open', %s, %s, %s, %s) RETURNING id", (branch_id, summary, description, applyfilters, applyparentfilters))

        review = dbutils.Review.fromId(db, cursor.fetchone()[0])

        cursor.execute("INSERT INTO reviewusers (review, uid, owner) VALUES (%s, %s, TRUE)", (review.id, user.id))

        if reviewfilters is not None:
            cursor.executemany("""INSERT INTO reviewfilters (review, uid, path, type, creator)
                                       VALUES (%s, %s, %s, %s, %s)""",
                               [(review.id, filter_user_id, filter_path, filter_type, user.id)
                                for filter_user_id, filter_path, filter_type, filter_delegate in reviewfilters])

        is_opt_in = False

        if recipientfilters is not None:
            cursor.executemany("INSERT INTO reviewrecipientfilters (review, uid, include) VALUES (%s, %s, %s)",
                               [(review.id, filter_user_id, filter_include)
                                for filter_user_id, filter_include in recipientfilters])

            for filter_user_id, filter_include in recipientfilters:
                if filter_user_id is None and not filter_include:
                    is_opt_in = True

        addCommitsToReview(db, user, review, commits, new_review=True)

        if from_branch_name is not None:
            cursor.execute("UPDATE branches SET review=%s WHERE repository=%s AND name=%s", (review.id, repository.id, from_branch_name))

        # Reload to get list of changesets added by addCommitsToReview().
        review = dbutils.Review.fromId(db, review.id)

        pending_mails = []
        recipients = review.getRecipients(db)
        for to_user in recipients:
            pending_mails.extend(mail.sendReviewCreated(db, user, to_user, recipients, review))

        if not is_opt_in:
            recipient_by_id = dict((to_user.id, to_user) for to_user in recipients)

            cursor.execute("""SELECT userpreferences.uid, userpreferences.repository,
                                     userpreferences.filter, userpreferences.integer
                                FROM userpreferences
                     LEFT OUTER JOIN filters ON (filters.id=userpreferences.filter)
                               WHERE userpreferences.item='review.defaultOptOut'
                                 AND userpreferences.uid=ANY (%s)
                                 AND (userpreferences.filter IS NULL
                                   OR filters.repository=%s)
                                 AND (userpreferences.repository IS NULL
                                   OR userpreferences.repository=%s)""",
                           (recipient_by_id.keys(), repository.id, repository.id))

            user_settings = {}
            has_filter_settings = False

            for user_id, repository_id, filter_id, integer in cursor:
                settings = user_settings.setdefault(user_id, [None, None, {}])
                value = bool(integer)

                if repository_id is None and filter_id is None:
                    settings[0] = value
                elif repository_id is not None:
                    settings[1] = value
                else:
                    settings[2][filter_id] = value
                    has_filter_settings = True

            if has_filter_settings:
                filters = Filters()
                filters.setFiles(db, review=review)

            for user_id, (global_default, repository_default, filter_settings) in user_settings.items():
                to_user = recipient_by_id[user_id]
                opt_out = None

                if repository_default is not None:
                    opt_out = repository_default
                elif global_default is not None:
                    opt_out = global_default

                if filter_settings:
                    # Policy:
                    #
                    # If all of the user's filters that matched files in the
                    # review have review.defaultOptOut enabled, then opt out.
                    # When determining this, any review filters of the user's
                    # that match files in the review count as filters that don't
                    # have the review.defaultOptOut enabled.
                    #
                    # If any of the user's filters that matched files in the
                    # review have review.defaultOptOut disabled, then don't opt
                    # out.  When determining this, review filters are ignored.
                    #
                    # Otherwise, ignore the filter settings, and go with either
                    # the user's per-repository or global setting (as set
                    # above.)

                    filters.load(db, review=review, user=to_user)

                    # A set of filter ids.  If None is in the set, the user has
                    # one or more review filters in the review.  (These do not
                    # have ids.)
                    active_filters = filters.getActiveFilters(to_user)

                    for filter_id in active_filters:
                        if filter_id is None:
                            continue
                        elif filter_id in filter_settings:
                            if not filter_settings[filter_id]:
                                opt_out = False
                                break
                        else:
                            break
                    else:
                        if None not in active_filters:
                            opt_out = True

                if opt_out:
                    cursor.execute("""INSERT INTO reviewrecipientfilters (review, uid, include)
                                           VALUES (%s, %s, FALSE)""",
                                   (review.id, to_user.id))

        db.commit()

        mail.sendPendingMails(pending_mails)

        return review
    except:
        if not via_push:
            repository.run("branch", "-D", branch_name)
        raise
Beispiel #36
0
def renderShowFile(req, db, user):
    cursor = db.cursor()

    sha1 = req.getParameter("sha1")
    path = req.getParameter("path")
    line = req.getParameter("line", None)
    review_id = req.getParameter("review", None, filter=int)

    default_tabify = "yes" if user.getPreference(db, "commit.diff.visualTabs") else "no"
    tabify = req.getParameter("tabify", default_tabify) == "yes"

    if line is None:
        first, last = None, None
    else:
        if "-" in line:
            first, last = map(int, line.split("-"))
        else:
            first = last = int(line)

        context = req.getParameter("context", user.getPreference(db, "commit.diff.contextLines"), int)

        first_with_context = max(1, first - context)
        last_with_context = last + context

    if user.getPreference(db, "commit.diff.compactMode"): default_compact = "yes"
    else: default_compact = "no"

    compact = req.getParameter("compact", default_compact) == "yes"

    if len(path) == 0 or path[-1:] == "/":
        raise page.utils.DisplayMessage(
            title="Invalid path parameter",
            body="<p>The path must be non-empty and must not end with a <code>/</code>.</p>",
            html=True)
    if path[0] == '/':
        full_path = path
        if path != "/": path = path[1:]
    else:
        full_path = "/" + path
        if not path: path = "/"

    if review_id is None:
        review = None
        repository_arg = req.getParameter("repository", "")
        if repository_arg:
            repository = gitutils.Repository.fromParameter(db, repository_arg)
        else:
            repository = gitutils.Repository.fromSHA1(db, sha1)
    else:
        review = dbutils.Review.fromId(db, review_id)
        repository = review.repository

    document = htmlutils.Document(req)

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

    if review:
        page.utils.generateHeader(body, db, user, lambda target: review_utils.renderDraftItems(db, user, review, target), extra_links=[("r/%d" % review.id, "Back to Review")])
    else:
        page.utils.generateHeader(body, db, user)

    document.addExternalStylesheet("resource/showfile.css")
    document.addInternalStylesheet(htmlutils.stripStylesheet(user.getResource(db, "syntax.css")[1], compact))

    commit = gitutils.Commit.fromSHA1(db, repository, sha1)
    file_sha1 = commit.getFileSHA1(full_path)
    file_id = dbutils.find_file(db, path=path)

    if file_sha1 is None:
        raise page.utils.DisplayMessage(
            title="File does not exist",
            body=("<p>There is no file named <code>%s</code> in the commit "
                  "<a href='/showcommit?repository=%s&amp;sha1=%s'>"
                  "<code>%s</code></a>.</p>"
                  % (htmlutils.htmlify(textutils.escape(full_path)),
                     htmlutils.htmlify(repository.name),
                     htmlutils.htmlify(sha1), htmlutils.htmlify(sha1[:8]))),
            html=True)

    file = diff.File(file_id, path, None, file_sha1, repository)

    # A new file ID might have been added to the database, so need to commit.
    db.commit()

    if file.canHighlight():
        requestHighlights(repository, { file.new_sha1: (file.path, file.getLanguage()) })

    file.loadNewLines(True, request_highlight=True)

    if review:
        document.addInternalScript(user.getJS())
        document.addInternalScript(review.getJS())
        document.addInternalScript("var changeset = { parent: { id: %(id)d, sha1: %(sha1)r }, child: { id: %(id)d, sha1: %(sha1)r } };" % { 'id': commit.getId(db), 'sha1': commit.sha1 })
        document.addInternalScript("var files = { %(id)d: { new_sha1: %(sha1)r }, %(sha1)r: { id: %(id)d, side: 'n' } };" % { 'id': file_id, 'sha1': file_sha1 })
        document.addExternalStylesheet("resource/review.css")
        document.addExternalScript("resource/review.js")

        cursor.execute("""SELECT DISTINCT commentchains.id
                            FROM commentchains
                            JOIN commentchainlines ON (commentchainlines.chain=commentchains.id)
                           WHERE commentchains.review=%s
                             AND commentchains.file=%s
                             AND commentchainlines.sha1=%s
                             AND ((commentchains.state!='draft' OR commentchains.uid=%s)
                              AND commentchains.state!='empty')
                        GROUP BY commentchains.id""",
                       (review.id, file_id, file_sha1, user.id))

        comment_chain_script = ""

        for (chain_id,) in cursor.fetchall():
            chain = review_comment.CommentChain.fromId(db, chain_id, user, review=review)
            chain.loadComments(db, user)

            comment_chain_script += "commentChains.push(%s);\n" % chain.getJSConstructor(file_sha1)

        if comment_chain_script:
            document.addInternalScript(comment_chain_script)

    document.addExternalStylesheet("resource/comment.css")
    document.addExternalScript("resource/comment.js")
    document.addExternalScript("resource/showfile.js")

    if tabify:
        document.addExternalStylesheet("resource/tabify.css")
        document.addExternalScript("resource/tabify.js")
        tabwidth = file.getTabWidth()
        indenttabsmode = file.getIndentTabsMode()

    if user.getPreference(db, "commit.diff.highlightIllegalWhitespace"):
        document.addInternalStylesheet(user.getResource(db, "whitespace.css")[1], compact)

    if first is not None:
        document.addInternalScript("var firstSelectedLine = %d, lastSelectedLine = %d;" % (first, last))

    target = body.div("main")

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

    table = target.table('file show expanded paleyellow', align='center', cellspacing=0)

    columns = table.colgroup()
    columns.col('edge')
    columns.col('linenr')
    columns.col('line')
    columns.col('middle')
    columns.col('middle')
    columns.col('line')
    columns.col('linenr')
    columns.col('edge')

    thead = table.thead()
    cell = thead.tr().td('h1', colspan=8)
    h1 = cell.h1()

    def make_url(url_path, path):
        params = { "sha1": sha1,
                   "path": path }
        if review is None:
            params["repository"] = str(repository.id)
        else:
            params["review"] = str(review.id)
        return "%s?%s" % (url_path, urllib.urlencode(params))

    h1.a("root", href=make_url("showtree", "/")).text("root")
    h1.span().text('/')

    components = path.split("/")
    for index, component in enumerate(components[:-1]):
        h1.a(href=make_url("showtree", "/".join(components[:index + 1]))).text(component, escape=True)
        h1.span().text('/')

    if first is not None:
        h1.a(href=make_url("showfile", "/".join(components))).text(components[-1], escape=True)
    else:
        h1.text(components[-1], escape=True)

    h1.span("right").a(href=("/download/%s?repository=%s&sha1=%s"
                             % (urllib.quote(path), repository.name, file_sha1)),
                       download=urllib.quote(path)).text("[download]")
    h1.span("right").a(href=("/download/%s?repository=%s&sha1=%s"
                             % (urllib.quote(path), repository.name, file_sha1))).text("[view]")

    table.tbody('spacer top').tr('spacer top').td(colspan=8).text()

    tbody = table.tbody("lines")

    yield document.render(stop=tbody, pretty=not compact)

    for linenr, line in enumerate(file.newLines(True)):
        linenr = linenr + 1
        highlight_class = ""

        if first is not None:
            if not (first_with_context <= linenr <= last_with_context): continue
            if linenr == first:
                highlight_class += " first-selected"
            if linenr == last:
                highlight_class += " last-selected"

        if tabify:
            line = htmlutils.tabify(line, tabwidth, indenttabsmode)

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

        row = tbody.tr("line context single", id="f%do%dn%d" % (file.id, linenr, linenr))
        row.td("edge").text()
        row.td("linenr old").text(linenr)
        row.td("line single whole%s" % highlight_class, id="f%dn%d" % (file.id, linenr), colspan=4).innerHTML(line)
        row.td("linenr new").text(linenr)
        row.td("edge").text()

        if linenr % 500:
            yield document.render(stop=tbody, pretty=not compact)

    table.tbody('spacer bottom').tr('spacer bottom').td(colspan=8).text()

    yield document.render(pretty=not compact)
Beispiel #37
0
def createReview(db, user, repository, commits, branch_name, summary, description, from_branch_name=None, via_push=False, reviewfilters=None, applyfilters=True, applyparentfilters=False, recipientfilters=None):
    cursor = db.cursor()

    if via_push:
        applyparentfilters = bool(user.getPreference(db, 'review.applyUpstreamFilters'))

    branch = dbutils.Branch.fromName(db, repository, branch_name)

    if branch is not None:
        raise OperationFailure(code="branchexists",
                               title="Invalid review branch name",
                               message="""\
<p>There is already a branch named <code>%s</code> in the repository.  You have
to select a different name.</p>

<p>If you believe the existing branch was created during an earlier (failed)
attempt to create this review, you can try to delete it from the repository
using the command<p>

<pre>  git push &lt;remote&gt; :%s</pre>

<p>and then press the "Submit Review" button on this page again."""
                               % (htmlutils.htmlify(branch_name), htmlutils.htmlify(branch_name)),
                               is_html=True)

    commitset = log_commitset.CommitSet(commits)

    if len(commitset.getHeads()) != 1:
        raise Exception, "invalid commit-set; multiple heads"

    head = commitset.getHeads().pop()

    if len(commitset.getTails()) != 1:
        tail_id = None
    else:
        tail_id = gitutils.Commit.fromSHA1(db, repository, commitset.getTails().pop()).getId(db)

    if not via_push:
        repository.branch(branch_name, head.sha1)

    try:
        cursor.execute("INSERT INTO branches (repository, name, head, tail, type) VALUES (%s, %s, %s, %s, 'review') RETURNING id", [repository.id, branch_name, head.getId(db), tail_id])

        branch_id = cursor.fetchone()[0]
        reachable_values = [(branch_id, commit.getId(db)) for commit in commits]

        cursor.executemany("INSERT INTO reachable (branch, commit) VALUES (%s, %s)", reachable_values)

        cursor.execute("INSERT INTO reviews (type, branch, state, summary, description, applyfilters, applyparentfilters) VALUES ('official', %s, 'open', %s, %s, %s, %s) RETURNING id", (branch_id, summary, description, applyfilters, applyparentfilters))

        review = dbutils.Review.fromId(db, cursor.fetchone()[0])

        cursor.execute("INSERT INTO reviewusers (review, uid, owner) VALUES (%s, %s, TRUE)", (review.id, user.id))

        if reviewfilters is not None:
            cursor.executemany("INSERT INTO reviewfilters (review, uid, directory, file, type, creator) VALUES (%s, %s, %s, %s, %s, %s)",
                               [(review.id, filter_user_id, filter_directory_id, filter_file_id, filter_type, user.id)
                                for filter_directory_id, filter_file_id, filter_type, filter_delegate, filter_user_id in reviewfilters])

        if recipientfilters is not None:
            cursor.executemany("INSERT INTO reviewrecipientfilters (review, uid, include) VALUES (%s, %s, %s)",
                               [(review.id, filter_user_id, filter_include)
                                for filter_user_id, filter_include in recipientfilters])

        addCommitsToReview(db, user, review, commits, new_review=True)

        if from_branch_name is not None:
            cursor.execute("UPDATE branches SET review=%s WHERE repository=%s AND name=%s", (review.id, repository.id, from_branch_name))

        # Reload to get list of changesets added by addCommitsToReview().
        review = dbutils.Review.fromId(db, review.id)

        pending_mails = []
        recipients = review.getRecipients(db)
        for to_user in recipients:
            pending_mails.extend(mail.sendReviewCreated(db, user, to_user, recipients, review))

        db.commit()

        mail.sendPendingMails(pending_mails)

        return review
    except:
        if not via_push:
            repository.run("branch", "-D", branch_name)
        raise
Beispiel #38
0
def wrap(raw_source, mode):
    if mode == "json":
        return "\n".join(
            textutils.json_encode([[None, line]])
            for line in diff.parse.splitlines(raw_source))
    return htmlutils.htmlify(raw_source)
Beispiel #39
0
            req.setContentType("text/html")
            req.start()

            yield str(document)
            return
        except:
            error_message = traceback.format_exc()

            environ["wsgi.errors"].write(error_message)

            db.rollback()

            if not user or user.hasRole(db, "developer"):
                title = "You broke the system again:"
                body = error_message
                body_html = "<pre>%s</pre>" % htmlify(body)
            else:
                prefix = dbutils.getURLPrefix(db)

                x_forwarded_host = req.getRequestHeader("X-Forwarded-Host")
                if x_forwarded_host: prefix = "https://" + x_forwarded_host

                url = "%s/%s?%s" % (prefix, req.path, req.query)

                mailutils.sendExceptionMessage("wsgi", (
                    "User:   %s\nMethod: %s\nPath:   %s\nQuery:  %s\nURL:    %s\n\n%s"
                    % (req.user, req.method, req.path, req.query, url,
                       error_message)))

                title = "Darn! It seems we have a problem..."
                body = "A message has been sent to the system administrator(s) with details about the problem."
Beispiel #40
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
def basic():
    import htmlutils

    from operation.basictypes import OperationError, OperationFailure
    from operation.typechecker import (
        Optional, TypeChecker, TypeCheckerContext, BooleanChecker,
        StringChecker, RestrictedString, SHA1, IntegerChecker,
        RestrictedInteger, PositiveInteger, NonNegativeInteger, ArrayChecker,
        EnumerationChecker, VariantChecker, DictionaryChecker)

    # Check TypeChecker.make()'s handling of basic types.
    assert type(TypeChecker.make(bool)) is BooleanChecker
    assert type(TypeChecker.make(str)) is StringChecker
    assert type(TypeChecker.make(int)) is IntegerChecker
    assert type(TypeChecker.make([bool])) is ArrayChecker
    assert type(TypeChecker.make(set(["foo", "bar"]))) is EnumerationChecker
    assert type(TypeChecker.make(set([bool, str, int]))) is VariantChecker
    assert type(TypeChecker.make({ "foo": bool })) is DictionaryChecker

    # Check TypeChecker.make()'s handling of TypeChecker sub-classes and
    # instances thereof.
    assert isinstance(TypeChecker.make(BooleanChecker), BooleanChecker)
    boolean_checker = BooleanChecker()
    assert TypeChecker.make(boolean_checker) is boolean_checker

    def check(checker, *values):
        checker = TypeChecker.make(checker)
        results = []
        for value in values:
            converted = checker(value, TypeCheckerContext(None, None, None))
            results.append(value if converted is None else converted)
        return results

    def should_match(checker, *values, **kwargs):
        results = check(checker, *values)
        if "result" in kwargs:
            expected_result = kwargs["result"]
            for result in results:
                assert result == expected_result, \
                    "%r != %r" % (result, expected_result)

    def should_not_match(checker, *values, **expected):
        for value in values:
            try:
                check(checker, copy.deepcopy(value))
            except (OperationError, OperationFailure) as error:
                error = json.loads(str(error))
                for key, value in expected.items():
                    if isinstance(value, str):
                        value = set([value])
                    assert error.get(key) in value, \
                        ("%s: %r not among %r" % (key, error.get(key), value))
            else:
                assert False, "checker allowed value incorrectly: %r" % value

    # Check some simple things that should be accepted.
    should_match(bool, True, False)
    should_match(str, "", "foo")
    should_match(int, -2**31, -1, 0, 1, 2**31)
    should_match([bool], [], [True, False])
    should_match([str], ["", "foo"])
    should_match([int], [-2**31, -1, 0, 1, 2**31])
    should_match(set(["foo", "bar"]), "foo", "bar")
    should_match(set([bool, str, int]),
                 True, False, "", "foo", -2**31, -1, 0, 1, 2**31)

    # Check some equally simple things that shouldn't be accepted.
    should_not_match(bool, 10, "foo",
                     error="invalid input: data is not a boolean")
    should_not_match(str, True, 10,
                     error="invalid input: data is not a string")
    should_not_match(int, True, "foo", 0.5,
                     error="invalid input: data is not an integer")
    should_not_match([bool], [True, 10], [False, "foo"],
                     error="invalid input: data[1] is not a boolean")
    should_not_match([str], ["", True], ["foo", 10],
                     error="invalid input: data[1] is not a string")
    should_not_match([int], [0, True], [10, "foo"],
                     error="invalid input: data[1] is not an integer")
    should_not_match(set(["foo", "bar"]), "fie",
                     error="invalid input: data is not valid")
    should_not_match(set(["foo", "bar"]), True, 10,
                     error="invalid input: data is not a string")
    should_not_match(set([bool, str, int]), [True], ["foo"], [10],
                     error="data is of invalid type")

    # Check some dictionary checkers.
    should_match({ "b": bool, "s": str, "i": int },
                 { "b": True, "s": "foo", "i": 10 })
    should_match({ "req": bool, "opt": Optional(bool) },
                 { "req": True, "opt": False },
                 { "req": False })
    should_not_match({ "b": bool }, { "b": "foo" }, { "b": 10 },
                     error="invalid input: data.b is not a boolean")
    should_not_match({ "b": bool }, { "i": 10 },
                     error="invalid input: data.b missing")
    should_not_match({ "b": bool }, { "b": True, "i": 10 },
                     error="invalid input: data.i was not used")
    should_not_match({ "b": Optional(bool) }, { "b": "foo" }, { "b": 10 },
                     error="invalid input: data.b is not a boolean")

    # Check suffixed variant checker in dictionary.
    id_or_name = VariantChecker({ "id": int, "name": str })
    should_match({ "thing": id_or_name },
                 { "thing": 10 },
                 { "thing_id": 10 },
                 result={ "thing": 10 })
    should_match({ "thing": id_or_name },
                 { "thing": "foo" },
                 { "thing_name": "foo" },
                 result={ "thing": "foo" })
    should_not_match({ "thing": id_or_name },
                     { "thing_id": "foo" },
                     error="invalid input: data.thing_id is not an integer")
    should_not_match({ "thing": id_or_name },
                     { "thing_name": 10 },
                     error="invalid input: data.thing_name is not a string")
    should_not_match({ "thing": id_or_name },
                     { "thing_id": 10,
                       "thing_name": "foo" },
                     error=("invalid input: data.thing_id was not used",
                            "invalid input: data.thing_name was not used"))

    # Check some RestrictedString types.
    should_match(RestrictedString, "", "foo")
    should_match(RestrictedString(minlength=0), "", "foo")
    should_match(RestrictedString(minlength=3), "foo")
    should_match(RestrictedString(maxlength=0), "")
    should_match(RestrictedString(maxlength=3), "", "foo")
    should_match(RestrictedString(minlength=0, maxlength=3), "", "foo")
    should_match(RestrictedString(allowed=lambda c: False), "")
    should_match(RestrictedString(allowed=lambda c: True), "", "foo")
    should_match(RestrictedString(allowed=lambda c: c in "foo"), "", "foo")
    should_not_match(RestrictedString(), True, 10,
                     error="invalid input: data is not a string")
    should_not_match(
        RestrictedString(minlength=1), "",
        code="paramtooshort:data",
        title="Invalid data",
        message="invalid input: data must be at least 1 characters long")
    should_not_match(
        RestrictedString(maxlength=2), "foo",
        code="paramtoolong:data",
        title="Invalid data",
        message="invalid input: data must be at most 2 characters long")
    should_not_match(
        RestrictedString(allowed=lambda c: False), "foo",
        code="paramcontainsillegalchar:data",
        title="Invalid data",
        message="invalid input: data may not contain the characters 'f', 'o'")
    should_not_match(
        RestrictedString(allowed=lambda c: False, ui_name="gazonk"), "foo",
        code="paramcontainsillegalchar:data",
        title="Invalid gazonk",
        message="invalid input: gazonk may not contain the characters 'f', 'o'")

    # Check SHA1.
    sha1 = "0123456789abcdefABCDEF0123456789abcdefAB"
    should_match(SHA1, *[sha1[:length] for length in range(4, 41)])
    should_not_match(SHA1, True, 10,
                     error="invalid input: data is not a string")
    for ch in range(0, 256):
        ch = chr(ch)
        if ch in sha1:
            continue
        should_not_match(
            SHA1, "012" + ch,
            message=htmlutils.htmlify(
                "invalid input: data may not contain the character %r" % ch))
    should_not_match(
        SHA1, "012",
        message="invalid input: data must be at least 4 characters long")
    should_not_match(
        SHA1, "0" * 41,
        message="invalid input: data must be at most 40 characters long")

    # Check some RestrictedInteger types.
    should_match(RestrictedInteger, -2**31, -1, 0, 1, 2**31)
    should_match(RestrictedInteger(minvalue=-2**31), -2**31, -1, 0, 1, 2**31)
    should_match(RestrictedInteger(minvalue=0), 0, 1, 2**31)
    should_match(RestrictedInteger(maxvalue=0), -2**31, -1, 0)
    should_match(RestrictedInteger(maxvalue=2**31), -2**31, -1, 0, 1, 2**31)
    should_match(RestrictedInteger(minvalue=0, maxvalue=0), 0)
    should_not_match(RestrictedInteger(), True, "foo",
                     error="invalid input: data is not an integer")
    should_not_match(RestrictedInteger(minvalue=0), -2**31, -1,
                     code="valuetoolow:data",
                     title="Invalid data parameter",
                     message="invalid input: data must be 0 or higher")
    should_not_match(RestrictedInteger(maxvalue=0), 1, 2**31,
                     code="valuetoohigh:data",
                     title="Invalid data parameter",
                     message="invalid input: data must be 0 or lower")
    should_not_match(RestrictedInteger(minvalue=1, ui_name="gazonk"), 0,
                     code="valuetoolow:data",
                     title="Invalid gazonk parameter",
                     message="invalid input: gazonk must be 1 or higher")

    # Check NonNegativeInteger.
    should_match(NonNegativeInteger, 0, 1, 2**31)
    should_not_match(NonNegativeInteger, True, "foo",
                     error="invalid input: data is not an integer")
    should_not_match(NonNegativeInteger, -2**31, -1,
                     code="valuetoolow:data",
                     title="Invalid data parameter",
                     message="invalid input: data must be 0 or higher")

    # Check PositiveInteger.
    should_match(PositiveInteger, 1, 2**31)
    should_not_match(PositiveInteger, True, "foo",
                     error="invalid input: data is not an integer")
    should_not_match(PositiveInteger, -2**31, -1, 0,
                     code="valuetoolow:data",
                     title="Invalid data parameter",
                     message="invalid input: data must be 1 or higher")
Beispiel #42
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
Beispiel #43
0
def process_request(environ, start_response):
    request_start = time.time()

    db = dbutils.Database()
    user = None

    try:
        try:
            req = request.Request(db, environ, start_response)
            req.setUser(db)

            if req.user is None:
                if configuration.base.AUTHENTICATION_MODE == "host":
                    user = dbutils.User.makeAnonymous()
                elif configuration.base.SESSION_TYPE == "httpauth":
                    req.requestHTTPAuthentication()
                    return []
                elif req.path.startswith("externalauth/"):
                    provider_name = req.path[len("externalauth/"):]
                    raise request.DoExternalAuthentication(provider_name)
                elif req.path.startswith("oauth/"):
                    provider_name = req.path[len("oauth/"):]
                    if provider_name in auth.PROVIDERS:
                        provider = auth.PROVIDERS[provider_name]
                        if isinstance(provider, auth.OAuthProvider):
                            if finishOAuth(db, req, provider):
                                return []
                elif configuration.base.SESSION_TYPE == "cookie":
                    if req.cookies.get("has_sid") == "1":
                        req.ensureSecure()
                    if configuration.base.ALLOW_ANONYMOUS_USER \
                            or req.path in request.INSECURE_PATHS \
                            or req.path.startswith("static-resource/"):
                        user = dbutils.User.makeAnonymous()
                    # Don't try to redirect POST requests to the login page.
                    elif req.method == "GET":
                        if configuration.base.AUTHENTICATION_MODE == "critic":
                            raise request.NeedLogin(req)
                        else:
                            raise request.DoExternalAuthentication(
                                configuration.base.AUTHENTICATION_MODE,
                                req.getTargetURL())
                if not user:
                    req.setStatus(403)
                    req.start()
                    return []
            else:
                try:
                    user = dbutils.User.fromName(db, req.user)
                except dbutils.NoSuchUser:
                    if configuration.base.AUTHENTICATION_MODE == "host":
                        email = getUserEmailAddress(req.user)
                        user = dbutils.User.create(
                            db, req.user, req.user, email, email_verified=None)
                        db.commit()
                    else:
                        # This can't really happen.
                        raise

            user.loadPreferences(db)

            if user.status == 'retired':
                cursor = db.cursor()
                cursor.execute("UPDATE users SET status='current' WHERE id=%s", (user.id,))
                user = dbutils.User.fromId(db, user.id)
                db.commit()

            if not user.getPreference(db, "debug.profiling.databaseQueries"):
                db.disableProfiling()

            if not req.path:
                if user.isAnonymous():
                    location = "tutorial"
                else:
                    location = user.getPreference(db, "defaultPage")

                if req.query:
                    location += "?" + req.query

                req.setStatus(307)
                req.addResponseHeader("Location", location)
                req.start()
                return []

            if req.path == "redirect":
                target = req.getParameter("target", "/")

                if req.method == "POST":
                    # Don't use HTTP redirect for POST requests.

                    req.setContentType("text/html")
                    req.start()

                    return ["<meta http-equiv='refresh' content='0; %s'>" % htmlify(target)]
                else:
                    raise request.MovedTemporarily(target)

            # Require a .git suffix on HTTP(S) repository URLs unless the user-
            # agent starts with "git/" (as Git's normally does.)
            #
            # Our objectives are:
            #
            # 1) Not to require Git's user-agent to be its default value, since
            #    the user might have to override it to get through firewalls.
            # 2) Never to send regular user requests to 'git http-backend' by
            #    mistake.
            #
            # This is a compromise.

            if req.getRequestHeader("User-Agent", "").startswith("git/"):
                suffix = None
            else:
                suffix = ".git"

            if handleRepositoryPath(db, req, user, suffix):
                db = None
                return []

            if req.path.startswith("!/"):
                req.path = req.path[2:]
            elif configuration.extensions.ENABLED:
                handled = extensions.role.page.execute(db, req, user)
                if isinstance(handled, basestring):
                    req.start()
                    return [handled]

            if req.path.startswith("static-resource/"):
                return handleStaticResource(req)

            if req.path.startswith("r/"):
                req.query = "id=" + req.path[2:] + ("&" + req.query if req.query else "")
                req.path = "showreview"

            if configuration.extensions.ENABLED:
                match = RE_EXTENSION_RESOURCE.match(req.path)
                if match:
                    content_type, resource = extensions.resource.get(req, db, user, match.group(1))
                    if resource:
                        req.setContentType(content_type)
                        if content_type.startswith("image/"):
                            req.addResponseHeader("Cache-Control", "max-age=3600")
                        req.start()
                        return [resource]
                    else:
                        req.setStatus(404)
                        req.start()
                        return []

            if req.path.startswith("download/"):
                operationfn = download
            else:
                operationfn = OPERATIONS.get(req.path)

            if operationfn:
                result = operationfn(req, db, user)

                if isinstance(result, (OperationResult, OperationError)):
                    req.setContentType("text/json")

                    if isinstance(result, OperationResult):
                        if db.profiling:
                            result.set("__profiling__", formatDBProfiling(db))
                            result.set("__time__", time.time() - request_start)
                elif not req.hasContentType():
                    req.setContentType("text/plain")

                req.start()

                if isinstance(result, unicode):
                    return [result.encode("utf8")]
                else:
                    return [str(result)]

            override_user = req.getParameter("user", None)

            while True:
                pagefn = PAGES.get(req.path)
                if pagefn:
                    try:
                        if not user.isAnonymous() and override_user:
                            user = dbutils.User.fromName(db, override_user)

                        req.setContentType("text/html")

                        result = pagefn(req, db, user)

                        if db.profiling and not (isinstance(result, str) or
                                                 isinstance(result, Document)):
                            source = ""
                            for fragment in result:
                                source += fragment
                            result = source

                        if isinstance(result, str) or isinstance(result, Document):
                            req.start()
                            result = str(result)
                            result += ("<!-- total request time: %.2f ms -->"
                                       % ((time.time() - request_start) * 1000))
                            if db.profiling:
                                result += ("<!--\n\n%s\n\n -->"
                                           % formatDBProfiling(db))
                            return [result]
                        else:
                            result = WrappedResult(db, req, user, result)
                            req.start()

                            # Prevent the finally clause below from closing the
                            # connection.  WrappedResult does it instead.
                            db = None

                            return result
                    except gitutils.NoSuchRepository as error:
                        raise page.utils.DisplayMessage(
                            title="Invalid URI Parameter!",
                            body=error.message)
                    except gitutils.GitReferenceError as error:
                        if error.ref:
                            raise page.utils.DisplayMessage(
                                title="Specified ref not found",
                                body=("There is no ref named \"%s\" in %s."
                                      % (error.ref, error.repository)))
                        elif error.sha1:
                            raise page.utils.DisplayMessage(
                                title="SHA-1 not found",
                                body=error.message)
                        else:
                            raise
                    except dbutils.NoSuchUser as error:
                        raise page.utils.DisplayMessage(
                            title="Invalid URI Parameter!",
                            body=error.message)
                    except dbutils.NoSuchReview as error:
                        raise page.utils.DisplayMessage(
                            title="Invalid URI Parameter!",
                            body=error.message)

                path = req.path

                if "/" in path:
                    repository = gitutils.Repository.fromName(db, path.split("/", 1)[0])
                    if repository: path = path.split("/", 1)[1]
                else:
                    repository = None

                def revparsePlain(item):
                    try: return gitutils.getTaggedCommit(repository, repository.revparse(item))
                    except: raise
                revparse = revparsePlain

                if repository is None:
                    review_id = req.getParameter("review", None, filter=int)

                    if review_id:
                        cursor = db.cursor()
                        cursor.execute("""SELECT repository
                                            FROM branches
                                            JOIN reviews ON (reviews.branch=branches.id)
                                           WHERE reviews.id=%s""",
                                       (review_id,))
                        row = cursor.fetchone()
                        if row:
                            repository = gitutils.Repository.fromId(db, row[0])
                            def revparseWithReview(item):
                                if re.match("^[0-9a-f]+$", item):
                                    cursor.execute("""SELECT sha1
                                                        FROM commits
                                                        JOIN changesets ON (changesets.child=commits.id)
                                                        JOIN reviewchangesets ON (reviewchangesets.changeset=changesets.id)
                                                       WHERE reviewchangesets.review=%s
                                                         AND commits.sha1 LIKE %s""",
                                                   (review_id, item + "%"))
                                    row = cursor.fetchone()
                                    if row: return row[0]
                                    else: return revparsePlain(item)
                            revparse = revparseWithReview

                if repository is None:
                    repository = gitutils.Repository.fromName(
                        db, user.getPreference(db, "defaultRepository"))

                    if gitutils.re_sha1.match(path):
                        if repository and not repository.iscommit(path):
                            repository = None

                        if not repository:
                            try:
                                repository = gitutils.Repository.fromSHA1(db, path)
                            except gitutils.GitReferenceError:
                                repository = None

                if repository:
                    try:
                        items = filter(None, map(revparse, path.split("..")))
                        query = None

                        if len(items) == 1:
                            query = ("repository=%d&sha1=%s"
                                     % (repository.id, items[0]))
                        elif len(items) == 2:
                            query = ("repository=%d&from=%s&to=%s"
                                     % (repository.id, items[0], items[1]))

                        if query:
                            if req.query:
                                query += "&" + req.query

                            req.query = query
                            req.path = "showcommit"
                            continue
                    except gitutils.GitReferenceError:
                        pass

                break

            req.setStatus(404)
            raise page.utils.DisplayMessage(
                title="Not found!",
                body="Page not handled: /%s" % path)
        except GeneratorExit:
            raise
        except page.utils.NotModified:
            req.setStatus(304)
            req.start()
            return []
        except request.MovedTemporarily as redirect:
            req.setStatus(307)
            req.addResponseHeader("Location", redirect.location)
            if redirect.no_cache:
                req.addResponseHeader("Cache-Control", "no-cache")
            req.start()
            return []
        except request.DoExternalAuthentication as command:
            command.execute(db, req)
            return []
        except request.MissingWSGIRemoteUser as error:
            # req object is not initialized yet.
            start_response("200 OK", [("Content-Type", "text/html")])
            return ["""\
<pre>error: Critic was configured with '--auth-mode host' but there was no
REMOTE_USER variable in the WSGI environ dict provided by the web server.

To fix this you can either reinstall Critic using '--auth-mode critic' (to let
Critic handle user authentication automatically), or you can configure user
authentication properly in the web server.  For apache2, the latter can be done
by adding the something like the following to the apache site configuration for
Critic:

        &lt;Location /&gt;
                AuthType Basic
                AuthName "Authentication Required"
                AuthUserFile "/path/to/critic-main.htpasswd.users"
                Require valid-user
        &lt;/Location&gt;

If you need more dynamic http authentication you can instead setup mod_wsgi with
a custom WSGIAuthUserScript directive.  This will cause the provided credentials
to be passed to a Python function called check_password() that you can implement
yourself.  This way you can validate the user/pass via any existing database or
for example an LDAP server.  For more information on setting up such
authentication in apache2, see:

  <a href="%(url)s">%(url)s</a></pre>""" % { "url": "http://code.google.com/p/modwsgi/wiki/AccessControlMechanisms#Apache_Authentication_Provider" }]
        except page.utils.DisplayMessage as message:
            if user is None:
                user = dbutils.User.makeAnonymous()

            document = page.utils.displayMessage(
                db, req, user, title=message.title, message=message.body,
                review=message.review, is_html=message.html)

            req.setContentType("text/html")
            req.start()

            return [str(document)]
        except Exception:
            # crash might be psycopg2.ProgrammingError so rollback to avoid
            # "InternalError: current transaction is aborted" inside handleException()
            if db and db.closed():
                db = None
            elif db:
                db.rollback()

            error_title, error_body = handleException(db, req, user)
            error_body = reflow("\n\n".join(error_body))
            error_message = "\n".join([error_title,
                                       "=" * len(error_title),
                                       "",
                                       error_body])

            assert not req.isStarted()

            req.setStatus(500)
            req.setContentType("text/plain")
            req.start()

            return [error_message]
    finally:
        if db:
            db.close()
Beispiel #44
0
def renderShowTree(req, db, user):
    cursor = db.cursor()

    sha1 = req.getParameter("sha1")
    path = req.getParameter("path", "/")
    review_id = req.getParameter("review", None, filter=int)

    if path[0] == '/':
        full_path = path
        if path != "/": path = path[1:]
    else:
        full_path = "/" + path
        if not path: path = "/"

    if review_id is None:
        review = None
        repository_arg = req.getParameter("repository", "")
        if repository_arg:
            repository = gitutils.Repository.fromParameter(db, repository_arg)
        else:
            repository = gitutils.Repository.fromSHA1(db, sha1)
    else:
        review = dbutils.Review.fromId(db, review_id)
        repository = review.repository

    document = htmlutils.Document(req)

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

    extra_links = []

    if review:
        extra_links.append(("r/%d" % review.id, "Back to Review"))

    page.utils.generateHeader(body, db, user, extra_links=extra_links)

    document.addExternalStylesheet("resource/showtree.css")

    if review:
        document.addInternalScript(review.getJS())

    target = body.div("main")

    table = target.table("tree paleyellow", align="center", cellspacing=0)
    table.col(width="10%")
    table.col(width="60%")
    table.col(width="20%")

    thead = table.thead()
    h1 = thead.tr().td('h1', colspan=3).h1()

    def make_url(url_path, path):
        params = { "sha1": sha1,
                   "path": path }
        if review is None:
            params["repository"] = str(repository.id)
        else:
            params["review"] = str(review.id)
        return "%s?%s" % (url_path, urllib.urlencode(params))

    if path == "/":
        h1.text("/")
    else:
        h1.a("root", href=make_url("showtree", "/")).text("root")
        h1.span().text('/')

        components = path.split("/")
        for index, component in enumerate(components[:-1]):
            h1.a(href=make_url("showtree", "/".join(components[:index + 1]))).text(component, escape=True)
            h1.span().text('/')

        h1.text(components[-1], escape=True)

    row = thead.tr()
    row.td('mode').text("Mode")
    row.td('name').text("Name")
    row.td('size').text("Size")

    tree = gitutils.Tree.fromPath(gitutils.Commit.fromSHA1(db, repository, sha1), full_path)

    if tree is None:
        raise page.utils.DisplayMessage(
            title="Directory does not exist",
            body=("<p>There is no directory named <code>%s</code> in the commit "
                  "<a href='/showcommit?repository=%s&amp;sha1=%s'>"
                  "<code>%s</code></a>.</p>"
                  % (htmlutils.htmlify(textutils.escape(full_path)),
                     htmlutils.htmlify(repository.name),
                     htmlutils.htmlify(sha1), htmlutils.htmlify(sha1[:8]))),
            html=True)

    def compareEntries(a, b):
        if a.type != b.type:
            if a.type == "tree": return -1
            else: return 1
        else:
            return cmp(a.name, b.name)

    tbody = table.tbody()

    for entry in sorted(tree, cmp=compareEntries):
        if entry.type in ("blob", "tree"):
            if entry.type == "blob":
                url_path = "showfile"
            else:
                url_path = "showtree"

            url = make_url(url_path, os.path.join(path, entry.name))
        else:
            url = None

        row = tbody.tr(entry.type)
        row.td('mode').text(str(entry.mode))

        if stat.S_ISLNK(entry.mode):
            cell = row.td('link', colspan=2)
            cell.span('name').text(entry.name, escape=True)
            cell.text(' -> ')
            cell.span('target').text(repository.fetch(entry.sha1).data)
        elif entry.type == "commit":
            row.td('name').text("%s (%s)" % (entry.name, entry.sha1), escape=True)
            row.td('size').text(entry.size)
        else:
            row.td('name').a(href=url).text(entry.name, escape=True)
            row.td('size').text(entry.size)

    return document
Beispiel #45
0
def renderShowBatch(req, db, user):
    batch_id = page.utils.getParameter(req, "batch", None, filter=int)
    review_id = page.utils.getParameter(req, "review", None, filter=int)

    cursor = db.cursor()

    if batch_id is None and review_id is None:
        return page.utils.displayMessage(db, req, user,
                                         "Missing argument: 'batch'")

    if batch_id:
        cursor.execute("SELECT review, uid, comment FROM batches WHERE id=%s",
                       (batch_id, ))

        row = cursor.fetchone()
        if not row:
            raise page.utils.DisplayMessage("Invalid batch ID: %d" % batch_id)

        review_id, author_id, chain_id = row
        author = dbutils.User.fromId(db, author_id)
    else:
        chain_id = None
        author = user

    review = dbutils.Review.fromId(db, review_id)

    if chain_id:
        batch_chain = review_comment.CommentChain.fromId(db,
                                                         chain_id,
                                                         user,
                                                         review=review)
        batch_chain.loadComments(db, user)
    else:
        batch_chain = None

    document = htmlutils.Document(req)

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

    page.utils.generateHeader(
        body,
        db,
        user,
        lambda target: review_utils.renderDraftItems(db, user, review, target),
        extra_links=[("r/%d" % review.id, "Back to Review")])

    document.addExternalStylesheet("resource/showreview.css")
    document.addExternalStylesheet("resource/showbatch.css")
    document.addExternalStylesheet("resource/review.css")
    document.addExternalStylesheet("resource/comment.css")
    document.addExternalScript("resource/review.js")
    document.addExternalScript("resource/comment.js")
    document.addInternalScript(user.getJS())
    document.addInternalScript(review.getJS())

    if batch_chain:
        document.addInternalScript(
            "commentChainById[%d] = %s;" %
            (batch_chain.id, batch_chain.getJSConstructor()))

    target = body.div("main")

    table = target.table('paleyellow basic comments', align='center')
    table.col(width='10%')
    table.col(width='80%')
    table.col(width='10%')
    table.tr().td('h1', colspan=3).h1().text("Review by %s" %
                                             htmlify(author.fullname))

    if batch_chain:
        batch_chain.loadComments(db, user)

        row = table.tr("line")
        row.td("heading").text("Comment:")
        row.td("value").preformatted().div("text").text(
            htmlify(batch_chain.comments[0].comment))
        row.td("status").text()

    def renderFiles(title, cursor):
        files = []

        for file_id, delete_count, insert_count in cursor.fetchall():
            files.append(
                (dbutils.describe_file(db,
                                       file_id), delete_count, insert_count))

        paths = []
        deleted = []
        inserted = []

        for path, delete_count, insert_count in sorted(files):
            paths.append(path)
            deleted.append(delete_count)
            inserted.append(insert_count)

        if paths:
            diff.File.eliminateCommonPrefixes(paths)

            row = table.tr("line")
            row.td("heading").text(title)

            files_table = row.td().table("files callout")
            headers = files_table.thead().tr()
            headers.th("path").text("Changed Files")
            headers.th("lines", colspan=2).text("Lines")

            files = files_table.tbody()
            for path, delete_count, insert_count in zip(
                    paths, deleted, inserted):
                file = files.tr()
                file.td("path").preformatted().innerHTML(path)
                file.td("lines").preformatted().text(
                    "-%d" % delete_count if delete_count else None)
                file.td("lines").preformatted().text(
                    "+%d" % insert_count if insert_count else None)

            row.td("status").text()

    def condition(table_name):
        if batch_id:
            return "%s.batch=%d" % (table_name, batch_id)
        else:
            return "review=%d AND %s.batch IS NULL AND %s.uid=%d" % (
                review.id, table_name, table_name, author.id)

    cursor.execute("""SELECT reviewfiles.file, SUM(deleted), SUM(inserted)
                        FROM reviewfiles
                        JOIN reviewfilechanges ON (reviewfilechanges.file=reviewfiles.id)
                       WHERE %s
                         AND reviewfilechanges.to_state='reviewed'
                    GROUP BY reviewfiles.file""" %
                   condition("reviewfilechanges"))
    renderFiles("Reviewed:", cursor)

    cursor.execute("""SELECT reviewfiles.file, SUM(deleted), SUM(inserted)
                        FROM reviewfiles
                        JOIN reviewfilechanges ON (reviewfilechanges.file=reviewfiles.id)
                       WHERE %s
                         AND reviewfilechanges.to_state='pending'
                    GROUP BY reviewfiles.file""" %
                   condition("reviewfilechanges"))
    renderFiles("Unreviewed:", cursor)

    def renderChains(title, cursor, replies):
        all_chains = [
            review_comment.CommentChain.fromId(db,
                                               chain_id,
                                               user,
                                               review=review)
            for (chain_id, ) in cursor
        ]

        if not all_chains:
            return

        for chain in all_chains:
            chain.loadComments(db, user)

        issue_chains = filter(lambda chain: chain.type == "issue", all_chains)
        draft_issues = filter(lambda chain: chain.state == "draft",
                              issue_chains)
        open_issues = filter(lambda chain: chain.state == "open", issue_chains)
        addressed_issues = filter(lambda chain: chain.state == "addressed",
                                  issue_chains)
        closed_issues = filter(lambda chain: chain.state == "closed",
                               issue_chains)
        note_chains = filter(lambda chain: chain.type == "note", all_chains)
        draft_notes = filter(
            lambda chain: chain.state == "draft" and chain != batch_chain,
            note_chains)
        open_notes = filter(
            lambda chain: chain.state == "open" and chain != batch_chain,
            note_chains)

        def renderChains(target, chains):
            for chain in chains:
                row = target.tr("comment")
                row.td("author").text(chain.user.fullname)
                row.td("title").a(href="showcomment?chain=%d" %
                                  chain.id).innerHTML(chain.leader())
                row.td("when").text(chain.when())

        def showcomments(filter_param):
            params = {"review": review.id, "filter": filter_param}
            if batch_id:
                params["batch"] = batch_id
            return htmlutils.URL("/showcomments", **params)

        if draft_issues or open_issues or addressed_issues or closed_issues:
            h2 = table.tr().td("h2", colspan=3).h2().text(title)
            if len(draft_issues) + len(open_issues) + len(
                    addressed_issues) + len(closed_issues) > 1:
                h2.a(href=showcomments("issues")).text("[display all]")

            if draft_issues:
                h3 = table.tr(id="draft-issues").td(
                    "h3", colspan=3).h3().text("Draft issues")
                if len(draft_issues) > 1:
                    h3.a(href=showcomments("draft-issues")).text(
                        "[display all]")
                renderChains(table, draft_issues)

            if batch_id is not None or replies:
                if open_issues:
                    h3 = table.tr(id="open-issues").td(
                        "h3", colspan=3).h3().text("Still open issues")
                    if batch_id and len(open_issues) > 1:
                        h3.a(href=showcomments("open-issues")).text(
                            "[display all]")
                    renderChains(table, open_issues)

                if addressed_issues:
                    h3 = table.tr(id="addressed-issues").td(
                        "h3", colspan=3).h3().text("Now addressed issues")
                    if batch_id and len(addressed_issues) > 1:
                        h3.a(href=showcomments("addressed-issues")).text(
                            "[display all]")
                    renderChains(table, addressed_issues)

                if closed_issues:
                    h3 = table.tr(id="closed-issues").td(
                        "h3", colspan=3).h3().text("Now closed issues")
                    if batch_id and len(closed_issues) > 1:
                        h3.a(href=showcomments("closed-issues")).text(
                            "[display all]")
                    renderChains(table, closed_issues)

        if draft_notes or open_notes:
            h2 = table.tr().td("h2", colspan=3).h2().text(title)
            if len(draft_notes) + len(open_notes) > 1:
                h2.a(href=showcomments("notes")).text("[display all]")

            if draft_notes:
                h3 = table.tr(id="draft-notes").td(
                    "h3", colspan=3).h3().text("Draft notes")
                if len(draft_notes) > 1:
                    h3.a(
                        href=showcomments("draft-notes")).text("[display all]")
                renderChains(table, draft_notes)

            if open_notes:
                h3 = table.tr(id="notes").td("h3",
                                             colspan=3).h3().text("Notes")
                if batch_id and len(open_notes) > 1:
                    h3.a(href=showcomments("open-notes")).text("[display all]")
                renderChains(table, open_notes)

    cursor.execute("SELECT id FROM commentchains WHERE %s AND type='issue'" %
                   condition("commentchains"))

    renderChains("Raised issues", cursor, False)

    cursor.execute("""SELECT commentchains.id
                        FROM commentchains
                        JOIN commentchainchanges ON (commentchainchanges.chain=commentchains.id)
                       WHERE %s
                         AND to_state='closed'""" %
                   condition("commentchainchanges"))

    renderChains("Resolved issues", cursor, False)

    cursor.execute("""SELECT commentchains.id
                        FROM commentchains
                        JOIN commentchainchanges ON (commentchainchanges.chain=commentchains.id)
                       WHERE %s
                         AND to_state='open'""" %
                   condition("commentchainchanges"))

    renderChains("Reopened issues", cursor, False)

    cursor.execute("""SELECT commentchains.id
                        FROM commentchains
                        JOIN commentchainchanges ON (commentchainchanges.chain=commentchains.id)
                       WHERE %s
                         AND to_type='issue'""" %
                   condition("commentchainchanges"))

    renderChains("Converted into issues", cursor, False)

    cursor.execute("""SELECT commentchains.id
                        FROM commentchains
                        JOIN commentchainchanges ON (commentchainchanges.chain=commentchains.id)
                       WHERE %s
                         AND to_type='note'""" %
                   condition("commentchainchanges"))

    renderChains("Converted into notes", cursor, False)

    cursor.execute("SELECT id FROM commentchains WHERE %s AND type='note'" %
                   condition("commentchains"))

    renderChains("Written notes", cursor, False)

    cursor.execute("""SELECT commentchains.id
                        FROM commentchains
                        JOIN comments ON (comments.chain=commentchains.id)
                       WHERE %s
                         AND comments.id!=commentchains.first_comment""" %
                   condition("comments"))

    renderChains("Replied to", cursor, True)

    return document
Beispiel #46
0
 def tag(cls, value): return "<b class='%s'>%s</b>" % (cls, htmlutils.htmlify(value))
 def tagm(cls, value):
Beispiel #47
0
 def __init__(self, code, title, message, is_html=False):
     self.__code = code
     self.__title = htmlutils.htmlify(title)
     self.__message = message if is_html else htmlutils.htmlify(message)
Beispiel #48
0
    def process(self,
                db,
                user,
                repository,
                branch,
                summary,
                commit_ids=None,
                commit_sha1s=None,
                applyfilters=True,
                applyparentfilters=True,
                reviewfilters=None,
                recipientfilters=None,
                description=None,
                frombranch=None,
                trackedbranch=None):
        if not branch.startswith("r/"):
            raise OperationFailure(
                code="invalidbranch",
                title="Invalid review branch name",
                message=
                "'%s' is not a valid review branch name; it must have a \"r/\" prefix."
                % branch)

        if reviewfilters is None:
            reviewfilters = []
        if recipientfilters is None:
            recipientfilters = {}

        components = branch.split("/")
        for index in range(1, len(components)):
            try:
                repository.revparse("refs/heads/%s" %
                                    "/".join(components[:index]))
            except gitutils.GitReferenceError:
                continue

            message = (
                "Cannot create branch with name<pre>%s</pre>since there is already a branch named<pre>%s</pre>in the repository."
                % (htmlutils.htmlify(branch),
                   htmlutils.htmlify("/".join(components[:index]))))
            raise OperationFailure(code="invalidbranch",
                                   title="Invalid review branch name",
                                   message=message,
                                   is_html=True)

        if commit_sha1s is not None:
            commits = [
                gitutils.Commit.fromSHA1(db, repository, commit_sha1)
                for commit_sha1 in commit_sha1s
            ]
        elif commit_ids is not None:
            commits = [
                gitutils.Commit.fromId(db, repository, commit_id)
                for commit_id in commit_ids
            ]
        else:
            commits = []

        commitset = CommitSet(commits)

        reviewfilters = parseReviewFilters(db, reviewfilters)
        recipientfilters = parseRecipientFilters(db, recipientfilters)

        review = createReview(db,
                              user,
                              repository,
                              commits,
                              branch,
                              summary,
                              description,
                              from_branch_name=frombranch,
                              reviewfilters=reviewfilters,
                              recipientfilters=recipientfilters,
                              applyfilters=applyfilters,
                              applyparentfilters=applyparentfilters)

        extensions_output = StringIO()
        kwargs = {}

        if configuration.extensions.ENABLED:
            if extensions.role.processcommits.execute(
                    db, user, review, commits, None,
                    commitset.getHeads().pop(), extensions_output):
                kwargs["extensions_output"] = extensions_output.getvalue(
                ).lstrip()

        if trackedbranch:
            cursor = db.cursor()

            cursor.execute(
                """SELECT 1
                                FROM knownremotes
                               WHERE url=%s
                                 AND pushing""", (trackedbranch["remote"], ))

            if cursor.fetchone():
                delay = "1 week"
            else:
                delay = "1 hour"

            cursor.execute(
                """INSERT INTO trackedbranches (repository, local_name, remote, remote_name, forced, delay)
                                   VALUES (%s, %s, %s, %s, false, %s)
                                RETURNING id""",
                (repository.id, branch, trackedbranch["remote"],
                 trackedbranch["name"], delay))

            trackedbranch_id = cursor.fetchone()[0]
            kwargs["trackedbranch_id"] = trackedbranch_id

            cursor.execute(
                """INSERT INTO trackedbranchusers (branch, uid)
                                   VALUES (%s, %s)""",
                (trackedbranch_id, user.id))

            db.commit()

            pid = int(
                open(configuration.services.BRANCHTRACKER["pidfile_path"]).
                read().strip())
            os.kill(pid, signal.SIGHUP)

        return OperationResult(review_id=review.id, **kwargs)
Beispiel #49
0
    def process(self, db, user, repository_name, remote, branch, upstream="refs/heads/master"):
        repository = gitutils.Repository.fromName(db, repository_name)

        cursor = db.cursor()

        # Check if any other repository is currently tracking branches from this
        # remote.  If that's the case, then the user most likely either selected
        # the wrong repository or entered the wrong remote.
        cursor.execute("""SELECT repositories.name
                            FROM repositories
                            JOIN trackedbranches ON (trackedbranches.repository=repositories.id)
                           WHERE repositories.id!=%s
                             AND trackedbranches.remote=%s""",
                       (repository.id, remote))
        for (other_name,) in cursor:
            raise OperationFailure(
                code="badremote",
                title="Bad remote!",
                message=("The remote <code>%s</code> appears to be related to "
                         "another repository on this server (<code>%s</code>).  "
                         "You most likely shouldn't be importing branches from "
                         "it into the selected repository (<code>%s</code>)."
                         % (htmlutils.htmlify(remote), htmlutils.htmlify(other_name),
                            htmlutils.htmlify(repository_name))),
                is_html=True)

        if not branch.startswith("refs/"):
            branch = "refs/heads/%s" % branch

        try:
            head_sha1 = repository.fetchTemporaryFromRemote(remote, branch)
        except gitutils.GitReferenceError:
            raise OperationFailure(
                code="refnotfound",
                title="Remote ref not found!",
                message=("Could not find the ref <code>%s</code> in the repository <code>%s</code>."
                         % (htmlutils.htmlify(branch), htmlutils.htmlify(remote))),
                is_html=True)
        except gitutils.GitCommandError as error:
            if error.output.splitlines()[0].endswith("does not appear to be a git repository"):
                raise OperationFailure(
                    code="invalidremote",
                    title="Invalid remote!",
                    message=("<code>%s</code> does not appear to be a valid Git repository."
                             % htmlutils.htmlify(remote)),
                    is_html=True)
            else:
                raise

        if upstream.startswith("refs/"):
            try:
                upstream_sha1 = repository.fetchTemporaryFromRemote(remote, upstream)
            except gitutils.GitReferenceError:
                raise OperationFailure(
                    code="refnotfound",
                    title="Remote ref not found!",
                    message=("Could not find the ref <code>%s</code> in the repository <code>%s</code>."
                             % (htmlutils.htmlify(upstream), htmlutils.htmlify(remote))),
                    is_html=True)
        else:
            try:
                upstream_sha1 = repository.revparse(upstream)
            except gitutils.GitReferenceError:
                raise OperationFailure(
                    code="refnotfound",
                    title="Local ref not found!",
                    message=("Could not find the ref <code>%s</code> in the repository <code>%s</code>."
                             % (htmlutils.htmlify(upstream), htmlutils.htmlify(str(repository)))),
                    is_html=True)

        try:
            resolved_upstream_sha1 = gitutils.getTaggedCommit(repository, upstream_sha1)
        except gitutils.GitReferenceError:
            resolved_upstream_sha1 = None

        if not resolved_upstream_sha1:
            raise OperationFailure(
                code="missingcommit",
                title="Upstream commit is missing!",
                message=("<p>Could not find the commit <code>%s</code> in the "
                         "repository <code>%s</code>.</p>"
                         "<p>Since it would have been fetched along with the "
                         "branch if it actually was a valid upstream commit, "
                         "this means it's not valid.</p>"
                         % (htmlutils.htmlify(upstream_sha1), htmlutils.htmlify(str(repository)))),
                is_html=True)

        commit_sha1s = repository.revlist(included=[head_sha1], excluded=[resolved_upstream_sha1])

        if not commit_sha1s:
            raise OperationFailure(
                code="emptybranch",
                title="Branch contains no commits!",
                message=("All commits referenced by <code>%s</code> are reachable from <code>%s</code>."
                         % (htmlutils.htmlify(branch), htmlutils.htmlify(upstream))),
                is_html=True)

        cursor.execute("SELECT id FROM commits WHERE sha1=ANY (%s)", (commit_sha1s,))

        return OperationResult(commit_ids=[commit_id for (commit_id,) in cursor],
                               head_sha1=head_sha1, upstream_sha1=resolved_upstream_sha1)
Beispiel #50
0
    def process(self, db, user, name, path, mirror=None):
        if not user.hasRole(db, "repositories"):
            raise OperationFailure(code="notallowed",
                                   title="Not allowed!",
                                   message="Only users with the 'repositories' role can add new repositories.")
        if name.endswith(".git"):
            raise OperationFailure(code="badsuffix_name",
                                   title="Invalid short name",
                                   message="The short name must not end with .git")

        if name == "r":
            raise OperationFailure(code="invalid_name",
                                   title="Invalid short name",
                                   message="The short name 'r' is not allowed since corresponding /REPOSHORTNAME/{SHA1|BRANCH} URLs would conflict with r/REVIEW_ID URLs.")

        path = path.strip("/").rsplit("/", 1)

        if len(path) == 2: base, repository_name = path
        else: base, repository_name = None, path[0]

        if base:
            main_base_path = os.path.join(configuration.paths.GIT_DIR, base)
        else:
            main_base_path = configuration.paths.GIT_DIR

        main_path = os.path.join(main_base_path, repository_name + ".git")

        cursor = db.cursor()
        cursor.execute("""SELECT name FROM repositories WHERE path=%s""", (main_path,))
        row = cursor.fetchone()
        if row:
            raise OperationFailure(code="duplicaterepository",
                                   title="Duplicate repository",
                                   message="The specified path is already used by repository %s" % row[0])
        cursor.execute("""SELECT name FROM repositories WHERE name=%s""", (name,))
        row = cursor.fetchone()
        if row:
            raise OperationFailure(code="duplicateshortname",
                                   title="Duplicate short name",
                                   message="The specified short name is already in use, please select a different short name.")

        if not os.path.isdir(main_base_path):
            os.makedirs(main_base_path, mode=0775)

        def git(arguments, cwd):
            argv = [configuration.executables.GIT] + arguments
            git = subprocess.Popen(argv, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=cwd)
            stdout, stderr = git.communicate()
            if git.returncode != 0:
                raise gitutils.GitError("unexpected output from '%s': %s" % (" ".join(argv), stderr))

        if mirror:
            try:
                subprocess.check_output([configuration.executables.GIT, "ls-remote", mirror["remote_url"]], stderr=subprocess.STDOUT)
            except subprocess.CalledProcessError as e:
                raise OperationFailure(code="failedreadremote",
                                       title="Failed to read source repository",
                                       message="Critic failed to read from the specified source repository. The error reported from git " +
                                               "(when running as the system user '%s') was: <pre>%s</pre>" % (configuration.base.SYSTEM_USER_NAME, htmlutils.htmlify(e.output)),
                                       is_html=True)

        git(["init", "--bare", "--shared", repository_name + ".git"], cwd=main_base_path)
        git(["config", "receive.denyNonFastforwards", "false"], cwd=main_path)
        git(["config", "critic.name", name], cwd=main_path)

        if configuration.debug.IS_QUICKSTART:
            git(["config", "critic.socket", os.path.join(configuration.paths.SOCKETS_DIR, "githook.unix")], cwd=main_path)

        os.symlink(os.path.join(configuration.paths.INSTALL_DIR, "hooks", "pre-receive"), os.path.join(main_path, "hooks", "pre-receive"))

        cursor.execute("""INSERT INTO repositories (name, path)
                               VALUES (%s, %s)
                            RETURNING id""",
                       (name, main_path))
        repository_id = cursor.fetchone()[0]

        if mirror:
            cursor.execute("""INSERT INTO trackedbranches (repository, local_name, remote, remote_name, forced, delay)
                                   VALUES (%s, '*', %s, '*', true, '1 day')""",
                           (repository_id, mirror["remote_url"]))

            cursor.execute("""INSERT INTO trackedbranches (repository, local_name, remote, remote_name, forced, delay)
                                   VALUES (%s, %s, %s, %s, true, '1 day')""",
                           (repository_id, mirror["local_branch"], mirror["remote_url"], mirror["remote_branch"]))

            git(["symbolic-ref", "HEAD", "refs/heads/" + mirror["local_branch"]], cwd=main_path)

        db.commit()

        if mirror:
            pid = int(open(configuration.services.BRANCHTRACKER["pidfile_path"]).read().strip())
            os.kill(pid, signal.SIGHUP)

        return OperationResult()
Beispiel #51
0
 def __init__(self, code, title, message, is_html=False):
     self.__code = code
     self.__title = htmlutils.htmlify(title)
     self.__message = message if is_html else htmlutils.htmlify(message)
Beispiel #52
0
    def process(self, db, user, repository, branch, summary, commit_ids=None,
                commit_sha1s=None, applyfilters=True, applyparentfilters=True,
                reviewfilters=None, recipientfilters=None, description=None,
                frombranch=None, trackedbranch=None):
        if not branch.startswith("r/"):
            raise OperationFailure(code="invalidbranch",
                                   title="Invalid review branch name",
                                   message="'%s' is not a valid review branch name; it must have a \"r/\" prefix." % branch)

        if reviewfilters is None:
            reviewfilters = []
        if recipientfilters is None:
            recipientfilters = {}

        components = branch.split("/")
        for index in range(1, len(components)):
            try:
                repository.revparse("refs/heads/%s" % "/".join(components[:index]))
            except gitutils.GitReferenceError:
                continue

            message = ("Cannot create branch with name<pre>%s</pre>since there is already a branch named<pre>%s</pre>in the repository." %
                       (htmlutils.htmlify(branch), htmlutils.htmlify("/".join(components[:index]))))
            raise OperationFailure(code="invalidbranch",
                                   title="Invalid review branch name",
                                   message=message,
                                   is_html=True)

        if commit_sha1s is not None:
            commits = [gitutils.Commit.fromSHA1(db, repository, commit_sha1) for commit_sha1 in commit_sha1s]
        elif commit_ids is not None:
            commits = [gitutils.Commit.fromId(db, repository, commit_id) for commit_id in commit_ids]
        else:
            commits = []

        commitset = CommitSet(commits)

        reviewfilters = parseReviewFilters(db, reviewfilters)
        recipientfilters = parseRecipientFilters(db, recipientfilters)

        review = createReview(db, user, repository, commits, branch, summary, description,
                              from_branch_name=frombranch,
                              reviewfilters=reviewfilters,
                              recipientfilters=recipientfilters,
                              applyfilters=applyfilters,
                              applyparentfilters=applyparentfilters)

        extensions_output = StringIO()
        kwargs = {}

        if configuration.extensions.ENABLED:
            if extensions.role.processcommits.execute(db, user, review, commits, None, commitset.getHeads().pop(), extensions_output):
                kwargs["extensions_output"] = extensions_output.getvalue().lstrip()

        if trackedbranch:
            cursor = db.cursor()

            cursor.execute("""SELECT 1
                                FROM knownremotes
                               WHERE url=%s
                                 AND pushing""",
                           (trackedbranch["remote"],))

            if cursor.fetchone():
                delay = "1 week"
            else:
                delay = "1 hour"

            cursor.execute("""INSERT INTO trackedbranches (repository, local_name, remote, remote_name, forced, delay)
                                   VALUES (%s, %s, %s, %s, false, %s)
                                RETURNING id""",
                           (repository.id, branch, trackedbranch["remote"], trackedbranch["name"], delay))

            trackedbranch_id = cursor.fetchone()[0]

            cursor.execute("""INSERT INTO trackedbranchusers (branch, uid)
                                   VALUES (%s, %s)""",
                           (trackedbranch_id, user.id))

            db.commit()

            pid = int(open(configuration.services.BRANCHTRACKER["pidfile_path"]).read().strip())
            os.kill(pid, signal.SIGHUP)

        return OperationResult(review_id=review.id, **kwargs)
Beispiel #53
0
def basic():
    import htmlutils

    from operation.basictypes import OperationError, OperationFailure
    from operation.typechecker import (
        Optional, TypeChecker, TypeCheckerContext, BooleanChecker,
        StringChecker, RestrictedString, SHA1, IntegerChecker,
        RestrictedInteger, PositiveInteger, NonNegativeInteger, ArrayChecker,
        EnumerationChecker, VariantChecker, DictionaryChecker)

    # Check TypeChecker.make()'s handling of basic types.
    assert type(TypeChecker.make(bool)) is BooleanChecker
    assert type(TypeChecker.make(str)) is StringChecker
    assert type(TypeChecker.make(int)) is IntegerChecker
    assert type(TypeChecker.make([bool])) is ArrayChecker
    assert type(TypeChecker.make(set(["foo", "bar"]))) is EnumerationChecker
    assert type(TypeChecker.make(set([bool, str, int]))) is VariantChecker
    assert type(TypeChecker.make({"foo": bool})) is DictionaryChecker

    # Check TypeChecker.make()'s handling of TypeChecker sub-classes and
    # instances thereof.
    assert isinstance(TypeChecker.make(BooleanChecker), BooleanChecker)
    boolean_checker = BooleanChecker()
    assert TypeChecker.make(boolean_checker) is boolean_checker

    def check(checker, *values):
        checker = TypeChecker.make(checker)
        results = []
        for value in values:
            converted = checker(value, TypeCheckerContext(None, None, None))
            results.append(value if converted is None else converted)
        return results

    def should_match(checker, *values, **kwargs):
        results = check(checker, *values)
        if "result" in kwargs:
            expected_result = kwargs["result"]
            for result in results:
                assert result == expected_result, \
                    "%r != %r" % (result, expected_result)

    def should_not_match(checker, *values, **expected):
        for value in values:
            try:
                check(checker, copy.deepcopy(value))
            except (OperationError, OperationFailure) as error:
                error = json.loads(str(error))
                for key, value in expected.items():
                    if isinstance(value, str):
                        value = set([value])
                    assert error.get(key) in value, \
                        ("%s: %r not among %r" % (key, error.get(key), value))
            else:
                assert False, "checker allowed value incorrectly: %r" % value

    # Check some simple things that should be accepted.
    should_match(bool, True, False)
    should_match(str, "", "foo")
    should_match(int, -2**31, -1, 0, 1, 2**31)
    should_match([bool], [], [True, False])
    should_match([str], ["", "foo"])
    should_match([int], [-2**31, -1, 0, 1, 2**31])
    should_match(set(["foo", "bar"]), "foo", "bar")
    should_match(set([bool, str, int]), True, False, "", "foo", -2**31, -1, 0,
                 1, 2**31)

    # Check some equally simple things that shouldn't be accepted.
    should_not_match(bool,
                     10,
                     "foo",
                     error="invalid input: data is not a boolean")
    should_not_match(str,
                     True,
                     10,
                     error="invalid input: data is not a string")
    should_not_match(int,
                     True,
                     "foo",
                     0.5,
                     error="invalid input: data is not an integer")
    should_not_match([bool], [True, 10], [False, "foo"],
                     error="invalid input: data[1] is not a boolean")
    should_not_match([str], ["", True], ["foo", 10],
                     error="invalid input: data[1] is not a string")
    should_not_match([int], [0, True], [10, "foo"],
                     error="invalid input: data[1] is not an integer")
    should_not_match(set(["foo", "bar"]),
                     "fie",
                     error="invalid input: data is not valid")
    should_not_match(set(["foo", "bar"]),
                     True,
                     10,
                     error="invalid input: data is not a string")
    should_not_match(set([bool, str, int]), [True], ["foo"], [10],
                     error="data is of invalid type")

    # Check some dictionary checkers.
    should_match({
        "b": bool,
        "s": str,
        "i": int
    }, {
        "b": True,
        "s": "foo",
        "i": 10
    })
    should_match({
        "req": bool,
        "opt": Optional(bool)
    }, {
        "req": True,
        "opt": False
    }, {"req": False})
    should_not_match({"b": bool}, {"b": "foo"}, {"b": 10},
                     error="invalid input: data.b is not a boolean")
    should_not_match({"b": bool}, {"i": 10},
                     error="invalid input: data.b missing")
    should_not_match({"b": bool}, {
        "b": True,
        "i": 10
    },
                     error="invalid input: data.i was not used")
    should_not_match({"b": Optional(bool)}, {"b": "foo"}, {"b": 10},
                     error="invalid input: data.b is not a boolean")

    # Check suffixed variant checker in dictionary.
    id_or_name = VariantChecker({"id": int, "name": str})
    should_match({"thing": id_or_name}, {"thing": 10}, {"thing_id": 10},
                 result={"thing": 10})
    should_match({"thing": id_or_name}, {"thing": "foo"},
                 {"thing_name": "foo"},
                 result={"thing": "foo"})
    should_not_match({"thing": id_or_name}, {"thing_id": "foo"},
                     error="invalid input: data.thing_id is not an integer")
    should_not_match({"thing": id_or_name}, {"thing_name": 10},
                     error="invalid input: data.thing_name is not a string")
    should_not_match({"thing": id_or_name}, {
        "thing_id": 10,
        "thing_name": "foo"
    },
                     error=("invalid input: data.thing_id was not used",
                            "invalid input: data.thing_name was not used"))

    # Check some RestrictedString types.
    should_match(RestrictedString, "", "foo")
    should_match(RestrictedString(minlength=0), "", "foo")
    should_match(RestrictedString(minlength=3), "foo")
    should_match(RestrictedString(maxlength=0), "")
    should_match(RestrictedString(maxlength=3), "", "foo")
    should_match(RestrictedString(minlength=0, maxlength=3), "", "foo")
    should_match(RestrictedString(allowed=lambda c: False), "")
    should_match(RestrictedString(allowed=lambda c: True), "", "foo")
    should_match(RestrictedString(allowed=lambda c: c in "foo"), "", "foo")
    should_not_match(RestrictedString(),
                     True,
                     10,
                     error="invalid input: data is not a string")
    should_not_match(
        RestrictedString(minlength=1),
        "",
        code="paramtooshort:data",
        title="Invalid data",
        message="invalid input: data must be at least 1 characters long")
    should_not_match(
        RestrictedString(maxlength=2),
        "foo",
        code="paramtoolong:data",
        title="Invalid data",
        message="invalid input: data must be at most 2 characters long")
    should_not_match(
        RestrictedString(allowed=lambda c: False),
        "foo",
        code="paramcontainsillegalchar:data",
        title="Invalid data",
        message="invalid input: data may not contain the characters 'f', 'o'")
    should_not_match(
        RestrictedString(allowed=lambda c: False, ui_name="gazonk"),
        "foo",
        code="paramcontainsillegalchar:data",
        title="Invalid gazonk",
        message="invalid input: gazonk may not contain the characters 'f', 'o'"
    )

    # Check SHA1.
    sha1 = "0123456789abcdefABCDEF0123456789abcdefAB"
    should_match(SHA1, *[sha1[:length] for length in range(4, 41)])
    should_not_match(SHA1,
                     True,
                     10,
                     error="invalid input: data is not a string")
    for ch in range(0, 256):
        ch = chr(ch)
        if ch in sha1:
            continue
        should_not_match(
            SHA1,
            "012" + ch,
            message=htmlutils.htmlify(
                "invalid input: data may not contain the character %r" % ch))
    should_not_match(
        SHA1,
        "012",
        message="invalid input: data must be at least 4 characters long")
    should_not_match(
        SHA1,
        "0" * 41,
        message="invalid input: data must be at most 40 characters long")

    # Check some RestrictedInteger types.
    should_match(RestrictedInteger, -2**31, -1, 0, 1, 2**31)
    should_match(RestrictedInteger(minvalue=-2**31), -2**31, -1, 0, 1, 2**31)
    should_match(RestrictedInteger(minvalue=0), 0, 1, 2**31)
    should_match(RestrictedInteger(maxvalue=0), -2**31, -1, 0)
    should_match(RestrictedInteger(maxvalue=2**31), -2**31, -1, 0, 1, 2**31)
    should_match(RestrictedInteger(minvalue=0, maxvalue=0), 0)
    should_not_match(RestrictedInteger(),
                     True,
                     "foo",
                     error="invalid input: data is not an integer")
    should_not_match(RestrictedInteger(minvalue=0),
                     -2**31,
                     -1,
                     code="valuetoolow:data",
                     title="Invalid data parameter",
                     message="invalid input: data must be 0 or higher")
    should_not_match(RestrictedInteger(maxvalue=0),
                     1,
                     2**31,
                     code="valuetoohigh:data",
                     title="Invalid data parameter",
                     message="invalid input: data must be 0 or lower")
    should_not_match(RestrictedInteger(minvalue=1, ui_name="gazonk"),
                     0,
                     code="valuetoolow:data",
                     title="Invalid gazonk parameter",
                     message="invalid input: gazonk must be 1 or higher")

    # Check NonNegativeInteger.
    should_match(NonNegativeInteger, 0, 1, 2**31)
    should_not_match(NonNegativeInteger,
                     True,
                     "foo",
                     error="invalid input: data is not an integer")
    should_not_match(NonNegativeInteger,
                     -2**31,
                     -1,
                     code="valuetoolow:data",
                     title="Invalid data parameter",
                     message="invalid input: data must be 0 or higher")

    # Check PositiveInteger.
    should_match(PositiveInteger, 1, 2**31)
    should_not_match(PositiveInteger,
                     True,
                     "foo",
                     error="invalid input: data is not an integer")
    should_not_match(PositiveInteger,
                     -2**31,
                     -1,
                     0,
                     code="valuetoolow:data",
                     title="Invalid data parameter",
                     message="invalid input: data must be 1 or higher")
Beispiel #54
0
def process_request(environ, start_response):
    request_start = time.time()

    critic = api.critic.startSession()
    db = critic.database
    user = None

    try:
        try:
            req = request.Request(db, environ, start_response)
            req.setUser(db)

            if req.user is None:
                if configuration.base.AUTHENTICATION_MODE == "host":
                    user = dbutils.User.makeAnonymous()
                elif configuration.base.SESSION_TYPE == "httpauth":
                    req.requestHTTPAuthentication()
                    return []
                elif req.path.startswith("externalauth/"):
                    provider_name = req.path[len("externalauth/"):]
                    raise request.DoExternalAuthentication(provider_name)
                elif req.path.startswith("oauth/"):
                    provider_name = req.path[len("oauth/"):]
                    if provider_name in auth.PROVIDERS:
                        provider = auth.PROVIDERS[provider_name]
                        if isinstance(provider, auth.OAuthProvider):
                            if finishOAuth(db, req, provider):
                                return []
                elif configuration.base.SESSION_TYPE == "cookie":
                    if req.cookies.get("has_sid") == "1":
                        req.ensureSecure()
                    if configuration.base.ALLOW_ANONYMOUS_USER \
                            or req.path in request.INSECURE_PATHS \
                            or req.path.startswith("static-resource/"):
                        user = dbutils.User.makeAnonymous()
                    # Don't try to redirect POST requests to the login page.
                    elif req.method == "GET":
                        if configuration.base.AUTHENTICATION_MODE == "critic":
                            raise request.NeedLogin(req)
                        else:
                            raise request.DoExternalAuthentication(
                                configuration.base.AUTHENTICATION_MODE,
                                req.getTargetURL())
                if not user:
                    req.setStatus(403)
                    req.start()
                    return []
            else:
                try:
                    user = dbutils.User.fromName(db, req.user)
                except dbutils.NoSuchUser:
                    if configuration.base.AUTHENTICATION_MODE == "host":
                        email = getUserEmailAddress(req.user)
                        user = dbutils.User.create(db,
                                                   req.user,
                                                   req.user,
                                                   email,
                                                   email_verified=None)
                        db.commit()
                    else:
                        # This can't really happen.
                        raise

            if not user.isAnonymous():
                critic.setActualUser(api.user.fetch(critic, user_id=user.id))

            user.loadPreferences(db)

            if user.status == 'retired':
                cursor = db.cursor()
                cursor.execute("UPDATE users SET status='current' WHERE id=%s",
                               (user.id, ))
                user = dbutils.User.fromId(db, user.id)
                db.commit()

            if not user.getPreference(db, "debug.profiling.databaseQueries"):
                db.disableProfiling()

            if not req.path:
                if user.isAnonymous():
                    location = "tutorial"
                else:
                    location = user.getPreference(db, "defaultPage")

                if req.query:
                    location += "?" + req.query

                req.setStatus(307)
                req.addResponseHeader("Location", location)
                req.start()
                return []

            if req.path == "redirect":
                target = req.getParameter("target", "/")

                if req.method == "POST":
                    # Don't use HTTP redirect for POST requests.

                    req.setContentType("text/html")
                    req.start()

                    return [
                        "<meta http-equiv='refresh' content='0; %s'>" %
                        htmlify(target)
                    ]
                else:
                    raise request.MovedTemporarily(target)

            # Require a .git suffix on HTTP(S) repository URLs unless the user-
            # agent starts with "git/" (as Git's normally does.)
            #
            # Our objectives are:
            #
            # 1) Not to require Git's user-agent to be its default value, since
            #    the user might have to override it to get through firewalls.
            # 2) Never to send regular user requests to 'git http-backend' by
            #    mistake.
            #
            # This is a compromise.

            if req.getRequestHeader("User-Agent", "").startswith("git/"):
                suffix = None
            else:
                suffix = ".git"

            if handleRepositoryPath(db, req, user, suffix):
                db = None
                return []

            if req.path.startswith("!/"):
                req.path = req.path[2:]
            elif configuration.extensions.ENABLED:
                handled = extensions.role.page.execute(db, req, user)
                if isinstance(handled, basestring):
                    req.start()
                    return [handled]

            if req.path.startswith("static-resource/"):
                return handleStaticResource(req)

            if req.path.startswith("r/"):
                req.updateQuery({"id": [req.path[2:]]})
                req.path = "showreview"

            if configuration.extensions.ENABLED:
                match = RE_EXTENSION_RESOURCE.match(req.path)
                if match:
                    content_type, resource = extensions.resource.get(
                        req, db, user, match.group(1))
                    if resource:
                        req.setContentType(content_type)
                        if content_type.startswith("image/"):
                            req.addResponseHeader("Cache-Control",
                                                  "max-age=3600")
                        req.start()
                        return [resource]
                    else:
                        req.setStatus(404)
                        req.start()
                        return []

            if req.path.startswith("download/"):
                operationfn = download
            elif req.path == "api" or req.path.startswith("api/"):
                try:
                    result = jsonapi.handle(critic, req)
                except jsonapi.Error as error:
                    req.setStatus(error.http_status)
                    result = {
                        "error": {
                            "title": error.title,
                            "message": error.message
                        }
                    }
                else:
                    req.setStatus(200)

                accept_header = req.getRequestHeader("Accept")
                if accept_header == "application/vnd.api+json":
                    default_indent = None
                else:
                    default_indent = 2
                indent = req.getParameter("indent", default_indent, filter=int)
                if indent == 0:
                    # json.encode(..., indent=0) still gives line-breaks, just
                    # no indentation.  This is not so useful, so set indent to
                    # None instead, which disables formatting entirely.
                    indent = None

                req.setContentType("application/vnd.api+json")
                req.start()
                return [json_encode(result, indent=indent)]
            else:
                operationfn = OPERATIONS.get(req.path)

            if operationfn:
                result = operationfn(req, db, user)

                if isinstance(result, (OperationResult, OperationError)):
                    req.setContentType("text/json")

                    if isinstance(result, OperationResult):
                        if db.profiling:
                            result.set("__profiling__", formatDBProfiling(db))
                            result.set("__time__", time.time() - request_start)
                elif not req.hasContentType():
                    req.setContentType("text/plain")

                req.start()

                if isinstance(result, unicode):
                    return [result.encode("utf8")]
                else:
                    return [str(result)]

            override_user = req.getParameter("user", None)

            while True:
                pagefn = PAGES.get(req.path)
                if pagefn:
                    try:
                        if not user.isAnonymous() and override_user:
                            user = dbutils.User.fromName(db, override_user)

                        req.setContentType("text/html")

                        result = pagefn(req, db, user)

                        if db.profiling and not (isinstance(result, str) or
                                                 isinstance(result, Document)):
                            source = ""
                            for fragment in result:
                                source += fragment
                            result = source

                        if isinstance(result, str) or isinstance(
                                result, Document):
                            req.start()
                            result = str(result)
                            result += ("<!-- total request time: %.2f ms -->" %
                                       ((time.time() - request_start) * 1000))
                            if db.profiling:
                                result += ("<!--\n\n%s\n\n -->" %
                                           formatDBProfiling(db))
                            return [result]
                        else:
                            result = WrappedResult(db, req, user, result)
                            req.start()

                            # Prevent the finally clause below from closing the
                            # connection.  WrappedResult does it instead.
                            db = None

                            return result
                    except gitutils.NoSuchRepository as error:
                        raise page.utils.DisplayMessage(
                            title="Invalid URI Parameter!", body=error.message)
                    except gitutils.GitReferenceError as error:
                        if error.ref:
                            raise page.utils.DisplayMessage(
                                title="Specified ref not found",
                                body=("There is no ref named \"%s\" in %s." %
                                      (error.ref, error.repository)))
                        elif error.sha1:
                            raise page.utils.DisplayMessage(
                                title="SHA-1 not found", body=error.message)
                        else:
                            raise
                    except dbutils.NoSuchUser as error:
                        raise page.utils.DisplayMessage(
                            title="Invalid URI Parameter!", body=error.message)
                    except dbutils.NoSuchReview as error:
                        raise page.utils.DisplayMessage(
                            title="Invalid URI Parameter!", body=error.message)

                path = req.path

                if "/" in path:
                    repository = gitutils.Repository.fromName(
                        db,
                        path.split("/", 1)[0])
                    if repository: path = path.split("/", 1)[1]
                else:
                    repository = None

                def revparsePlain(item):
                    try:
                        return gitutils.getTaggedCommit(
                            repository, repository.revparse(item))
                    except:
                        raise

                revparse = revparsePlain

                if repository is None:
                    review_id = req.getParameter("review", None, filter=int)

                    if review_id:
                        cursor = db.cursor()
                        cursor.execute(
                            """SELECT repository
                                            FROM branches
                                            JOIN reviews ON (reviews.branch=branches.id)
                                           WHERE reviews.id=%s""",
                            (review_id, ))
                        row = cursor.fetchone()
                        if row:
                            repository = gitutils.Repository.fromId(db, row[0])

                            def revparseWithReview(item):
                                if re.match("^[0-9a-f]+$", item):
                                    cursor.execute(
                                        """SELECT sha1
                                                        FROM commits
                                                        JOIN changesets ON (changesets.child=commits.id)
                                                        JOIN reviewchangesets ON (reviewchangesets.changeset=changesets.id)
                                                       WHERE reviewchangesets.review=%s
                                                         AND commits.sha1 LIKE %s""",
                                        (review_id, item + "%"))
                                    row = cursor.fetchone()
                                    if row: return row[0]
                                    else: return revparsePlain(item)

                            revparse = revparseWithReview

                if repository is None:
                    repository = gitutils.Repository.fromName(
                        db, user.getPreference(db, "defaultRepository"))

                    if gitutils.re_sha1.match(path):
                        if repository and not repository.iscommit(path):
                            repository = None

                        if not repository:
                            try:
                                repository = gitutils.Repository.fromSHA1(
                                    db, path)
                            except gitutils.GitReferenceError:
                                repository = None

                if repository:
                    try:
                        items = filter(None, map(revparse, path.split("..")))
                        updated_query = {}

                        if len(items) == 1:
                            updated_query["repository"] = [repository.name]
                            updated_query["sha1"] = [items[0]]
                        elif len(items) == 2:
                            updated_query["repository"] = [repository.name]
                            updated_query["from"] = [items[0]]
                            updated_query["to"] = [items[1]]

                        if updated_query:
                            req.updateQuery(updated_query)
                            req.path = "showcommit"
                            continue
                    except gitutils.GitReferenceError:
                        pass

                break

            raise page.utils.DisplayMessage(title="Not found!",
                                            body="Page not handled: /%s" %
                                            path,
                                            status=404)
        except GeneratorExit:
            raise
        except page.utils.NotModified:
            req.setStatus(304)
            req.start()
            return []
        except request.MovedTemporarily as redirect:
            req.setStatus(307)
            req.addResponseHeader("Location", redirect.location)
            if redirect.no_cache:
                req.addResponseHeader("Cache-Control", "no-cache")
            req.start()
            return []
        except request.DoExternalAuthentication as command:
            command.execute(db, req)
            return []
        except request.MissingWSGIRemoteUser as error:
            # req object is not initialized yet.
            start_response("200 OK", [("Content-Type", "text/html")])
            return [
                """\
<pre>error: Critic was configured with '--auth-mode host' but there was no
REMOTE_USER variable in the WSGI environ dict provided by the web server.

To fix this you can either reinstall Critic using '--auth-mode critic' (to let
Critic handle user authentication automatically), or you can configure user
authentication properly in the web server.  For apache2, the latter can be done
by adding the something like the following to the apache site configuration for
Critic:

        &lt;Location /&gt;
                AuthType Basic
                AuthName "Authentication Required"
                AuthUserFile "/path/to/critic-main.htpasswd.users"
                Require valid-user
        &lt;/Location&gt;

If you need more dynamic http authentication you can instead setup mod_wsgi with
a custom WSGIAuthUserScript directive.  This will cause the provided credentials
to be passed to a Python function called check_password() that you can implement
yourself.  This way you can validate the user/pass via any existing database or
for example an LDAP server.  For more information on setting up such
authentication in apache2, see:

  <a href="%(url)s">%(url)s</a></pre>""" % {
                    "url":
                    "http://code.google.com/p/modwsgi/wiki/AccessControlMechanisms#Apache_Authentication_Provider"
                }
            ]
        except page.utils.DisplayMessage as message:
            if user is None:
                user = dbutils.User.makeAnonymous()

            document = page.utils.displayMessage(db,
                                                 req,
                                                 user,
                                                 title=message.title,
                                                 message=message.body,
                                                 review=message.review,
                                                 is_html=message.html)

            req.setContentType("text/html")
            req.setStatus(message.status)
            req.start()

            return [str(document)]
        except page.utils.DisplayFormattedText as formatted_text:
            if user is None:
                user = dbutils.User.makeAnonymous()

            document = page.utils.displayFormattedText(db, req, user,
                                                       formatted_text.source)

            req.setContentType("text/html")
            req.start()

            return [str(document)]
        except Exception:
            # crash might be psycopg2.ProgrammingError so rollback to avoid
            # "InternalError: current transaction is aborted" inside handleException()
            if db and db.closed():
                db = None
            elif db:
                db.rollback()

            error_title, error_body = handleException(db, req, user)
            error_body = reflow("\n\n".join(error_body))
            error_message = "\n".join(
                [error_title, "=" * len(error_title), "", error_body])

            assert not req.isStarted()

            req.setStatus(500)
            req.setContentType("text/plain")
            req.start()

            return [error_message]
    finally:
        if db:
            db.close()
Beispiel #55
0
def createReview(db,
                 user,
                 repository,
                 commits,
                 branch_name,
                 summary,
                 description,
                 from_branch_name=None,
                 via_push=False,
                 reviewfilters=None,
                 applyfilters=True,
                 applyparentfilters=False,
                 recipientfilters=None):
    cursor = db.cursor()

    if via_push:
        applyparentfilters = bool(
            user.getPreference(db, 'review.applyUpstreamFilters'))

    branch = dbutils.Branch.fromName(db, repository, branch_name)

    if branch is not None:
        raise OperationFailure(
            code="branchexists",
            title="Invalid review branch name",
            message="""\
<p>There is already a branch named <code>%s</code> in the repository.  You have
to select a different name.</p>

<p>If you believe the existing branch was created during an earlier (failed)
attempt to create this review, you can try to delete it from the repository
using the command<p>

<pre>  git push &lt;remote&gt; :%s</pre>

<p>and then press the "Submit Review" button on this page again.""" %
            (htmlutils.htmlify(branch_name), htmlutils.htmlify(branch_name)),
            is_html=True)

    if not commits:
        raise OperationFailure(
            code="nocommits",
            title="No commits specified",
            message="You need at least one commit to create a review.")

    commitset = log_commitset.CommitSet(commits)
    heads = commitset.getHeads()

    if len(heads) != 1:
        # There is really no plausible way for this error to occur.
        raise OperationFailure(
            code="disconnectedtree",
            title="Disconnected tree",
            message=("The specified commits do do not form a single connected "
                     "tree.  Creating a review of them is not supported."))

    head = heads.pop()

    if len(commitset.getTails()) != 1:
        tail_id = None
    else:
        tail_id = gitutils.Commit.fromSHA1(
            db, repository,
            commitset.getTails().pop()).getId(db)

    if not via_push:
        try:
            repository.createBranch(branch_name, head.sha1)
        except gitutils.GitCommandError as error:
            raise OperationFailure(
                code="branchfailed",
                title="Failed to create review branch",
                message=("<p><b>Output from git:</b></p>"
                         "<code style='padding-left: 1em'>%s</code>" %
                         htmlutils.htmlify(error.output)),
                is_html=True)

    createChangesetsForCommits(db, commits)

    try:
        cursor.execute(
            "INSERT INTO branches (repository, name, head, tail, type) VALUES (%s, %s, %s, %s, 'review') RETURNING id",
            [repository.id, branch_name,
             head.getId(db), tail_id])

        branch_id = cursor.fetchone()[0]
        reachable_values = [(branch_id, commit.getId(db))
                            for commit in commits]

        cursor.executemany(
            "INSERT INTO reachable (branch, commit) VALUES (%s, %s)",
            reachable_values)

        cursor.execute(
            "INSERT INTO reviews (type, branch, state, summary, description, applyfilters, applyparentfilters) VALUES ('official', %s, 'open', %s, %s, %s, %s) RETURNING id",
            (branch_id, summary, description, applyfilters,
             applyparentfilters))

        review = dbutils.Review.fromId(db, cursor.fetchone()[0])

        cursor.execute(
            "INSERT INTO reviewusers (review, uid, owner) VALUES (%s, %s, TRUE)",
            (review.id, user.id))

        if reviewfilters is not None:
            cursor.executemany(
                """INSERT INTO reviewfilters (review, uid, path, type, creator)
                                       VALUES (%s, %s, %s, %s, %s)""",
                [(review.id, filter_user_id, filter_path, filter_type, user.id)
                 for filter_user_id, filter_path, filter_type, filter_delegate
                 in reviewfilters])

        is_opt_in = False

        if recipientfilters is not None:
            cursor.executemany(
                "INSERT INTO reviewrecipientfilters (review, uid, include) VALUES (%s, %s, %s)",
                [(review.id, filter_user_id, filter_include)
                 for filter_user_id, filter_include in recipientfilters])

            for filter_user_id, filter_include in recipientfilters:
                if filter_user_id is None and not filter_include:
                    is_opt_in = True

        addCommitsToReview(db, user, review, commits, new_review=True)

        if from_branch_name is not None:
            cursor.execute(
                "UPDATE branches SET review=%s WHERE repository=%s AND name=%s",
                (review.id, repository.id, from_branch_name))

        # Reload to get list of changesets added by addCommitsToReview().
        review = dbutils.Review.fromId(db, review.id)

        pending_mails = []
        recipients = review.getRecipients(db)
        for to_user in recipients:
            pending_mails.extend(
                mail.sendReviewCreated(db, user, to_user, recipients, review))

        if not is_opt_in:
            recipient_by_id = dict(
                (to_user.id, to_user) for to_user in recipients)

            cursor.execute(
                """SELECT userpreferences.uid, userpreferences.repository,
                                     userpreferences.filter, userpreferences.integer
                                FROM userpreferences
                     LEFT OUTER JOIN filters ON (filters.id=userpreferences.filter)
                               WHERE userpreferences.item='review.defaultOptOut'
                                 AND userpreferences.uid=ANY (%s)
                                 AND (userpreferences.filter IS NULL
                                   OR filters.repository=%s)
                                 AND (userpreferences.repository IS NULL
                                   OR userpreferences.repository=%s)""",
                (recipient_by_id.keys(), repository.id, repository.id))

            user_settings = {}
            has_filter_settings = False

            for user_id, repository_id, filter_id, integer in cursor:
                settings = user_settings.setdefault(user_id, [None, None, {}])
                value = bool(integer)

                if repository_id is None and filter_id is None:
                    settings[0] = value
                elif repository_id is not None:
                    settings[1] = value
                else:
                    settings[2][filter_id] = value
                    has_filter_settings = True

            if has_filter_settings:
                filters = Filters()
                filters.setFiles(db, review=review)

            for user_id, (global_default, repository_default,
                          filter_settings) in user_settings.items():
                to_user = recipient_by_id[user_id]
                opt_out = None

                if repository_default is not None:
                    opt_out = repository_default
                elif global_default is not None:
                    opt_out = global_default

                if filter_settings:
                    # Policy:
                    #
                    # If all of the user's filters that matched files in the
                    # review have review.defaultOptOut enabled, then opt out.
                    # When determining this, any review filters of the user's
                    # that match files in the review count as filters that don't
                    # have the review.defaultOptOut enabled.
                    #
                    # If any of the user's filters that matched files in the
                    # review have review.defaultOptOut disabled, then don't opt
                    # out.  When determining this, review filters are ignored.
                    #
                    # Otherwise, ignore the filter settings, and go with either
                    # the user's per-repository or global setting (as set
                    # above.)

                    filters.load(db, review=review, user=to_user)

                    # A set of filter ids.  If None is in the set, the user has
                    # one or more review filters in the review.  (These do not
                    # have ids.)
                    active_filters = filters.getActiveFilters(to_user)

                    for filter_id in active_filters:
                        if filter_id is None:
                            continue
                        elif filter_id in filter_settings:
                            if not filter_settings[filter_id]:
                                opt_out = False
                                break
                        else:
                            break
                    else:
                        if None not in active_filters:
                            opt_out = True

                if opt_out:
                    cursor.execute(
                        """INSERT INTO reviewrecipientfilters (review, uid, include)
                                           VALUES (%s, %s, FALSE)""",
                        (review.id, to_user.id))

        db.commit()

        mail.sendPendingMails(pending_mails)

        return review
    except:
        if not via_push:
            repository.run("branch", "-D", branch_name)
        raise
Beispiel #56
0
def renderShowFile(req, db, user):
    cursor = db.cursor()

    sha1 = req.getParameter("sha1")
    path = req.getParameter("path")
    line = req.getParameter("line", None)
    review_id = req.getParameter("review", None, filter=int)

    default_tabify = "yes" if user.getPreference(
        db, "commit.diff.visualTabs") else "no"
    tabify = req.getParameter("tabify", default_tabify) == "yes"

    if line is None:
        first, last = None, None
    else:
        if "-" in line:
            first, last = map(int, line.split("-"))
        else:
            first = last = int(line)

        context = req.getParameter(
            "context", user.getPreference(db, "commit.diff.contextLines"), int)

        first_with_context = max(1, first - context)
        last_with_context = last + context

    if user.getPreference(db, "commit.diff.compactMode"):
        default_compact = "yes"
    else:
        default_compact = "no"

    compact = req.getParameter("compact", default_compact) == "yes"

    if len(path) == 0 or path[-1:] == "/":
        raise page.utils.DisplayMessage(
            title="Invalid path parameter",
            body=
            "<p>The path must be non-empty and must not end with a <code>/</code>.</p>",
            html=True)
    if path[0] == '/':
        full_path = path
        if path != "/": path = path[1:]
    else:
        full_path = "/" + path
        if not path: path = "/"

    if review_id is None:
        review = None
        repository_arg = req.getParameter("repository", "")
        if repository_arg:
            repository = gitutils.Repository.fromParameter(db, repository_arg)
        else:
            repository = gitutils.Repository.fromSHA1(db, sha1)
    else:
        review = dbutils.Review.fromId(db, review_id)
        repository = review.repository

    document = htmlutils.Document(req)

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

    if review:
        page.utils.generateHeader(body,
                                  db,
                                  user,
                                  lambda target: review_utils.renderDraftItems(
                                      db, user, review, target),
                                  extra_links=[("r/%d" % review.id,
                                                "Back to Review")])
    else:
        page.utils.generateHeader(body, db, user)

    document.addExternalStylesheet("resource/showfile.css")
    document.addInternalStylesheet(
        htmlutils.stripStylesheet(
            user.getResource(db, "syntax.css")[1], compact))

    commit = gitutils.Commit.fromSHA1(db, repository, sha1)
    file_sha1 = commit.getFileSHA1(full_path)
    file_id = dbutils.find_file(db, path=path)

    if file_sha1 is None:
        raise page.utils.DisplayMessage(
            title="File does not exist",
            body=("<p>There is no file named <code>%s</code> in the commit "
                  "<a href='/showcommit?repository=%s&amp;sha1=%s'>"
                  "<code>%s</code></a>.</p>" %
                  (htmlutils.htmlify(textutils.escape(full_path)),
                   htmlutils.htmlify(repository.name), htmlutils.htmlify(sha1),
                   htmlutils.htmlify(sha1[:8]))),
            html=True)

    file = diff.File(file_id, path, None, file_sha1, repository)

    # A new file ID might have been added to the database, so need to commit.
    db.commit()

    if file.canHighlight():
        requestHighlights(repository,
                          {file.new_sha1: (file.path, file.getLanguage())})

    file.loadNewLines(True, request_highlight=True)

    if review:
        document.addInternalScript(user.getJS())
        document.addInternalScript(review.getJS())
        document.addInternalScript(
            "var changeset = { parent: { id: %(id)d, sha1: %(sha1)r }, child: { id: %(id)d, sha1: %(sha1)r } };"
            % {
                'id': commit.getId(db),
                'sha1': commit.sha1
            })
        document.addInternalScript(
            "var files = { %(id)d: { new_sha1: %(sha1)r }, %(sha1)r: { id: %(id)d, side: 'n' } };"
            % {
                'id': file_id,
                'sha1': file_sha1
            })
        document.addExternalStylesheet("resource/review.css")
        document.addExternalScript("resource/review.js")

        cursor.execute(
            """SELECT DISTINCT commentchains.id
                            FROM commentchains
                            JOIN commentchainlines ON (commentchainlines.chain=commentchains.id)
                           WHERE commentchains.review=%s
                             AND commentchains.file=%s
                             AND commentchainlines.sha1=%s
                             AND ((commentchains.state!='draft' OR commentchains.uid=%s)
                              AND commentchains.state!='empty')
                        GROUP BY commentchains.id""",
            (review.id, file_id, file_sha1, user.id))

        comment_chain_script = ""

        for (chain_id, ) in cursor.fetchall():
            chain = review_comment.CommentChain.fromId(db,
                                                       chain_id,
                                                       user,
                                                       review=review)
            chain.loadComments(db, user)

            comment_chain_script += "commentChains.push(%s);\n" % chain.getJSConstructor(
                file_sha1)

        if comment_chain_script:
            document.addInternalScript(comment_chain_script)

    document.addExternalStylesheet("resource/comment.css")
    document.addExternalScript("resource/comment.js")
    document.addExternalScript("resource/showfile.js")

    if tabify:
        document.addExternalStylesheet("resource/tabify.css")
        document.addExternalScript("resource/tabify.js")
        tabwidth = file.getTabWidth()
        indenttabsmode = file.getIndentTabsMode()

    if user.getPreference(db, "commit.diff.highlightIllegalWhitespace"):
        document.addInternalStylesheet(
            user.getResource(db, "whitespace.css")[1], compact)

    if first is not None:
        document.addInternalScript(
            "var firstSelectedLine = %d, lastSelectedLine = %d;" %
            (first, last))

    target = body.div("main")

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

    table = target.table('file show expanded paleyellow',
                         align='center',
                         cellspacing=0)

    columns = table.colgroup()
    columns.col('edge')
    columns.col('linenr')
    columns.col('line')
    columns.col('middle')
    columns.col('middle')
    columns.col('line')
    columns.col('linenr')
    columns.col('edge')

    thead = table.thead()
    cell = thead.tr().td('h1', colspan=8)
    h1 = cell.h1()

    def make_url(url_path, path):
        params = {"sha1": sha1, "path": path}
        if review is None:
            params["repository"] = str(repository.id)
        else:
            params["review"] = str(review.id)
        return "%s?%s" % (url_path, urllib.urlencode(params))

    h1.a("root", href=make_url("showtree", "/")).text("root")
    h1.span().text('/')

    components = path.split("/")
    for index, component in enumerate(components[:-1]):
        h1.a(href=make_url("showtree", "/".join(components[:index + 1]))).text(
            component, escape=True)
        h1.span().text('/')

    if first is not None:
        h1.a(href=make_url("showfile", "/".join(components))).text(
            components[-1], escape=True)
    else:
        h1.text(components[-1], escape=True)

    h1.span("right").a(href=("/download/%s?repository=%s&sha1=%s" %
                             (urllib.quote(path), repository.name, file_sha1)),
                       download=urllib.quote(path)).text("[download]")
    h1.span("right").a(
        href=("/download/%s?repository=%s&sha1=%s" %
              (urllib.quote(path), repository.name, file_sha1))).text("[view]")

    table.tbody('spacer top').tr('spacer top').td(colspan=8).text()

    tbody = table.tbody("lines")

    yield document.render(stop=tbody, pretty=not compact)

    for linenr, line in enumerate(file.newLines(True)):
        linenr = linenr + 1
        highlight_class = ""

        if first is not None:
            if not (first_with_context <= linenr <= last_with_context):
                continue
            if linenr == first:
                highlight_class += " first-selected"
            if linenr == last:
                highlight_class += " last-selected"

        if tabify:
            line = htmlutils.tabify(line, tabwidth, indenttabsmode)

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

        row = tbody.tr("line context single",
                       id="f%do%dn%d" % (file.id, linenr, linenr))
        row.td("edge").text()
        row.td("linenr old").text(linenr)
        row.td("line single whole%s" % highlight_class,
               id="f%dn%d" % (file.id, linenr),
               colspan=4).innerHTML(line)
        row.td("linenr new").text(linenr)
        row.td("edge").text()

        if linenr % 500:
            yield document.render(stop=tbody, pretty=not compact)

    table.tbody('spacer bottom').tr('spacer bottom').td(colspan=8).text()

    yield document.render(pretty=not compact)
Beispiel #57
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
Beispiel #58
0
    def process(self,
                db,
                user,
                repository_name,
                remote,
                branch,
                upstream="refs/heads/master"):
        repository = gitutils.Repository.fromName(db, repository_name)

        cursor = db.cursor()

        # Check if any other repository is currently tracking branches from this
        # remote.  If that's the case, then the user most likely either selected
        # the wrong repository or entered the wrong remote.
        cursor.execute(
            """SELECT repositories.name
                            FROM repositories
                            JOIN trackedbranches ON (trackedbranches.repository=repositories.id)
                           WHERE repositories.id!=%s
                             AND trackedbranches.remote=%s""",
            (repository.id, remote))
        for (other_name, ) in cursor:
            raise OperationFailure(
                code="badremote",
                title="Bad remote!",
                message=(
                    "The remote <code>%s</code> appears to be related to "
                    "another repository on this server (<code>%s</code>).  "
                    "You most likely shouldn't be importing branches from "
                    "it into the selected repository (<code>%s</code>)." %
                    (htmlutils.htmlify(remote), htmlutils.htmlify(other_name),
                     htmlutils.htmlify(repository_name))),
                is_html=True)

        if not branch.startswith("refs/"):
            branch = "refs/heads/%s" % branch

        try:
            with repository.fetchTemporaryFromRemote(remote, branch) as sha1:
                head_sha1 = repository.keepalive(sha1)
        except gitutils.GitReferenceError as error:
            if error.repository:
                raise OperationFailure(
                    code="refnotfound",
                    title="Remote ref not found!",
                    message=
                    ("Could not find the ref <code>%s</code> in the repository <code>%s</code>."
                     % (htmlutils.htmlify(
                         error.ref), htmlutils.htmlify(error.repository))),
                    is_html=True)
            else:
                raise OperationFailure(
                    code="invalidref",
                    title="Invalid ref!",
                    message=("The specified ref is invalid: <code>%s</code>." %
                             htmlutils.htmlify(error.ref)),
                    is_html=True)
        except gitutils.GitCommandError as error:
            if error.output.splitlines()[0].endswith(
                    "does not appear to be a git repository"):
                raise OperationFailure(
                    code="invalidremote",
                    title="Invalid remote!",
                    message=
                    ("<code>%s</code> does not appear to be a valid Git repository."
                     % htmlutils.htmlify(remote)),
                    is_html=True)
            else:
                raise

        if upstream.startswith("refs/"):
            try:
                with repository.fetchTemporaryFromRemote(remote,
                                                         upstream) as sha1:
                    upstream_sha1 = repository.keepalive(sha1)
            except gitutils.GitReferenceError:
                raise OperationFailure(
                    code="refnotfound",
                    title="Remote ref not found!",
                    message=
                    ("Could not find the ref <code>%s</code> in the repository <code>%s</code>."
                     %
                     (htmlutils.htmlify(upstream), htmlutils.htmlify(remote))),
                    is_html=True)
        else:
            try:
                upstream_sha1 = repository.revparse(upstream)
            except gitutils.GitReferenceError:
                raise OperationFailure(
                    code="refnotfound",
                    title="Local ref not found!",
                    message=
                    ("Could not find the ref <code>%s</code> in the repository <code>%s</code>."
                     % (htmlutils.htmlify(upstream),
                        htmlutils.htmlify(str(repository)))),
                    is_html=True)

        try:
            resolved_upstream_sha1 = gitutils.getTaggedCommit(
                repository, upstream_sha1)
        except gitutils.GitReferenceError:
            resolved_upstream_sha1 = None

        if not resolved_upstream_sha1:
            raise OperationFailure(
                code="missingcommit",
                title="Upstream commit is missing!",
                message=("<p>Could not find the commit <code>%s</code> in the "
                         "repository <code>%s</code>.</p>"
                         "<p>Since it would have been fetched along with the "
                         "branch if it actually was a valid upstream commit, "
                         "this means it's not valid.</p>" %
                         (htmlutils.htmlify(upstream_sha1),
                          htmlutils.htmlify(str(repository)))),
                is_html=True)

        commit_sha1s = repository.revlist(included=[head_sha1],
                                          excluded=[resolved_upstream_sha1])

        if not commit_sha1s:
            raise OperationFailure(
                code="emptybranch",
                title="Branch contains no commits!",
                message=
                ("All commits referenced by <code>%s</code> are reachable from <code>%s</code>."
                 % (htmlutils.htmlify(branch), htmlutils.htmlify(upstream))),
                is_html=True)

        cursor.execute("SELECT id FROM commits WHERE sha1=ANY (%s)",
                       (commit_sha1s, ))

        return OperationResult(
            commit_ids=[commit_id for (commit_id, ) in cursor],
            head_sha1=head_sha1,
            upstream_sha1=resolved_upstream_sha1)