change(rendercomment): converted to use aurweb.db ORM

- Added aurweb.util.git_search.
    - Decoupled away from rendercomment for easier testability.
- Added aurweb.testing.git.GitRepository.
- Added templates/testing/{PKGBUILD,SRCINFO}.j2.
- Added aurweb.testing.git.GitRepository + `git` pytest fixture

Signed-off-by: Kevin Morris <kevr@0cost.org>
This commit is contained in:
Kevin Morris 2021-11-17 06:20:50 -08:00
parent 4b0cb0721d
commit 2d0e09cd63
No known key found for this signature in database
GPG key ID: F7E46DED420788F3
9 changed files with 398 additions and 195 deletions

View file

@ -7,13 +7,11 @@ import markdown
import pygit2
import aurweb.config
import aurweb.db
from aurweb import logging
from aurweb import db, logging, util
from aurweb.models import PackageComment
logger = logging.get_logger(__name__)
repo_path = aurweb.config.get('serve', 'repo-path')
commit_uri = aurweb.config.get('options', 'commit_uri')
class LinkifyExtension(markdown.extensions.Extension):
@ -64,6 +62,7 @@ class GitCommitsInlineProcessor(markdown.inlinepatterns.InlineProcessor):
"""
def __init__(self, md, head):
repo_path = aurweb.config.get('serve', 'repo-path')
self._repo = pygit2.Repository(repo_path)
self._head = head
super().__init__(r'\b([0-9a-f]{7,40})\b', md)
@ -74,13 +73,9 @@ class GitCommitsInlineProcessor(markdown.inlinepatterns.InlineProcessor):
# Unkwown OID; preserve the orginal text.
return (None, None, None)
prefixlen = 12
while prefixlen < 40:
if oid[:prefixlen] in self._repo:
break
prefixlen += 1
el = markdown.util.etree.Element('a')
commit_uri = aurweb.config.get("options", "commit_uri")
prefixlen = util.git_search(self._repo, oid)
el.set('href', commit_uri % (self._head, oid[:prefixlen]))
el.text = markdown.util.AtomicString(oid[:prefixlen])
return (el, m.start(0), m.end(0))
@ -116,49 +111,41 @@ class HeadingExtension(markdown.extensions.Extension):
md.treeprocessors.register(HeadingTreeprocessor(md), 'heading', 30)
def get_comment(conn, commentid):
cur = conn.execute('SELECT PackageComments.Comments, PackageBases.Name '
'FROM PackageComments INNER JOIN PackageBases '
'ON PackageBases.ID = PackageComments.PackageBaseID '
'WHERE PackageComments.ID = ?', [commentid])
return cur.fetchone()
def save_rendered_comment(comment: PackageComment, html: str):
with db.begin():
comment.RenderedComment = html
def save_rendered_comment(conn, commentid, html):
conn.execute('UPDATE PackageComments SET RenderedComment = ? WHERE ID = ?',
[html, commentid])
def update_comment_render_fastapi(comment: PackageComment) -> None:
update_comment_render(comment)
def update_comment_render_fastapi(comment):
conn = aurweb.db.ConnectionExecutor(
aurweb.db.get_engine().raw_connection())
update_comment_render(conn, comment.ID)
aurweb.db.refresh(comment)
def update_comment_render(comment: PackageComment) -> None:
text = comment.Comments
pkgbasename = comment.PackageBase.Name
def update_comment_render(conn, commentid):
text, pkgbase = get_comment(conn, commentid)
html = markdown.markdown(text, extensions=[
'fenced_code',
LinkifyExtension(),
FlysprayLinksExtension(),
GitCommitsExtension(pkgbase),
GitCommitsExtension(pkgbasename),
HeadingExtension()
])
allowed_tags = (bleach.sanitizer.ALLOWED_TAGS
+ ['p', 'pre', 'h4', 'h5', 'h6', 'br', 'hr'])
html = bleach.clean(html, tags=allowed_tags)
save_rendered_comment(conn, commentid, html)
conn.commit()
conn.close()
save_rendered_comment(comment, html)
db.refresh(comment)
def main():
commentid = int(sys.argv[1])
conn = aurweb.db.Connection()
update_comment_render(conn, commentid)
db.get_engine()
comment_id = int(sys.argv[1])
comment = db.query(PackageComment).filter(
PackageComment.ID == comment_id
).first()
update_comment_render(comment)
if __name__ == '__main__':

110
aurweb/testing/git.py Normal file
View file

@ -0,0 +1,110 @@
import os
import shlex
from subprocess import PIPE, Popen
from typing import Tuple
import py
from aurweb.models import Package
from aurweb.templates import base_template
from aurweb.testing.filelock import FileLock
class GitRepository:
"""
A Git repository class to be used for testing.
Expects a `tmpdir` fixture on construction, which an 'aur.git'
git repository will be created in. After this class is constructed,
users can call GitRepository.exec for git repository operations.
"""
def __init__(self, tmpdir: py.path.local):
self.file_lock = FileLock(tmpdir, "aur.git")
self.file_lock.lock(on_create=self._setup)
def _exec(self, cmdline: str, cwd: str) -> Tuple[int, str, str]:
args = shlex.split(cmdline)
proc = Popen(args, cwd=cwd, stdout=PIPE, stderr=PIPE)
out, err = proc.communicate()
return (proc.returncode, out.decode().strip(), err.decode().strip())
def _exec_repository(self, cmdline: str) -> Tuple[int, str, str]:
return self._exec(cmdline, cwd=str(self.file_lock.path))
def exec(self, cmdline: str) -> Tuple[int, str, str]:
return self._exec_repository(cmdline)
def _setup(self, path: str) -> None:
"""
Setup the git repository from scratch.
Create the `path` directory and run the INSTALL recommended
git initialization commands inside of it. Additionally, install
aurweb.git.update to {path}/hooks/update.
:param path: Repository path not yet created
"""
os.makedirs(path)
commands = [
"git init -q",
"git config --local transfer.hideRefs '^refs/'",
"git config --local --add transfer.hideRefs '!refs/'",
"git config --local --add transfer.hideRefs '!HEAD'",
"git config --local commit.gpgsign false",
"git config --local user.name 'Test User'",
"git config --local user.email 'test@example.org'",
]
for cmdline in commands:
return_code, out, err = self.exec(cmdline)
assert return_code == 0
# This is also done in the INSTALL script to give the `aur`
# ssh user permissions on the repository. We don't need it
# during testing, since our testing user will be controlling
# the repository. It is left here as a note.
# self.exec("chown -R aur .")
def commit(self, pkg: Package, message: str):
"""
Commit a Package record to the git repository.
This function generates a PKGBUILD and .SRCINFO based on
`pkg`, then commits them to the repository with the
`message` commit message.
:param pkg: Package instance
:param message: Commit message
:return: Output of `git rev-parse HEAD` after committing
"""
ref = f"refs/namespaces/{pkg.Name}/refs/heads/master"
rc, out, err = self.exec(f"git checkout -q --orphan {ref}")
assert rc == 0, f"{(rc, out, err)}"
# Path to aur.git repository.
repo = os.path.join(self.file_lock.path)
licenses = [f"'{p.License.Name}'" for p in pkg.package_licenses]
depends = [f"'{p.DepName}'" for p in pkg.package_dependencies]
pkgbuild = base_template("testing/PKGBUILD.j2")
pkgbuild_path = os.path.join(repo, "PKGBUILD")
with open(pkgbuild_path, "w") as f:
data = pkgbuild.render(pkg=pkg, licenses=licenses, depends=depends)
f.write(data)
srcinfo = base_template("testing/SRCINFO.j2")
srcinfo_path = os.path.join(repo, ".SRCINFO")
with open(srcinfo_path, "w") as f:
f.write(srcinfo.render(pkg=pkg))
rc, out, err = self.exec("git add PKGBUILD .SRCINFO")
assert rc == 0, f"{(rc, out, err)}"
rc, out, err = self.exec(f"git commit -q -m '{message}'")
assert rc == 0, f"{(rc, out, err)}"
# Return stdout of `git rev-parse HEAD`, which is the new commit hash.
return self.exec("git rev-parse HEAD")[1]

View file

@ -13,6 +13,7 @@ from urllib.parse import urlencode, urlparse
from zoneinfo import ZoneInfo
import fastapi
import pygit2
from email_validator import EmailNotValidError, EmailUndeliverableError, validate_email
from jinja2 import pass_context
@ -193,3 +194,19 @@ def file_hash(filepath: str, hash_function: Callable) -> str:
with open(filepath, "rb") as f:
hash_ = hash_function(f.read())
return hash_.hexdigest()
def git_search(repo: pygit2.Repository, commit_hash: str) -> int:
"""
Return the shortest prefix length matching `commit_hash` found.
:param repo: pygit2.Repository instance
:param commit_hash: Full length commit hash
:return: Shortest unique prefix length found
"""
prefixlen = 12
while prefixlen < len(commit_hash):
if commit_hash[:prefixlen] in repo:
break
prefixlen += 1
return prefixlen