Exemplo n.º 1
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) + "…/" +
                                   htmlutils.htmlify(
                                       textutils.escape(current[length:])))
                    else:
                        updated = htmlutils.htmlify(textutils.escape(current))

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

                previous = current

        return files
Exemplo n.º 2
0
    def text(self, value=None, preformatted=False, cdata=False, linkify=False, repository=None, escape=False):
        if linkify:
            assert not cdata

            if isinstance(linkify, Context):
                context = linkify
            else:
                context = Context(repository=repository)

            for linktype in ALL_LINKTYPES:
                if linktype.match(value):
                    url = linktype.linkify(value, context)
                    if url:
                        self.a(href=url).text(value, escape=escape)
                        break
            else:
                for word in re_linkify.split(value):
                    if word:
                        for linktype in ALL_LINKTYPES:
                            if linktype.match(word):
                                url = linktype.linkify(word, context)
                                if url:
                                    self.a(href=url).text(word, escape=escape)
                                    break
                        else:
                            self.text(word, preformatted, escape=escape)
        else:
            if escape:
                value = textutils.escape(value)
            self.__target.appendChild(Text(value, preformatted, cdata))
        return self
Exemplo n.º 3
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) + "…/" +
                                   htmlutils.htmlify(textutils.escape(current[length:])))
                    else:
                        updated = htmlutils.htmlify(textutils.escape(current))

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

                previous = current

        return files
Exemplo n.º 4
0
    def text(self,
             value=None,
             preformatted=False,
             cdata=False,
             linkify=False,
             repository=None,
             escape=False):
        if linkify:
            assert not cdata

            if isinstance(linkify, Context):
                context = linkify
            else:
                context = Context(repository=repository)

            for linktype in ALL_LINKTYPES:
                if linktype.match(value):
                    url = linktype.linkify(value, context)
                    if url:
                        self.a(href=url).text(value, escape=escape)
                        break
            else:
                for word in re_linkify.split(value):
                    if word:
                        for linktype in ALL_LINKTYPES:
                            if linktype.match(word):
                                url = linktype.linkify(word, context)
                                if url:
                                    self.a(href=url).text(word, escape=escape)
                                    break
                        else:
                            self.text(word, preformatted, escape=escape)
        else:
            if escape:
                value = textutils.escape(value)
            self.__target.appendChild(Text(value, preformatted, cdata))
        return self
Exemplo n.º 5
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)
Exemplo n.º 6
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)
Exemplo n.º 7
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")

    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
Exemplo n.º 8
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