Export deunicode() function from textutils
Import ProcessTimeout/ProcessError from the right module

They are defined by communicate, not extensions.execute.
Don't ignore empty extension pages

An extension page role can skip a request by not writing anything, in
which case extensions.role.page.execute() returns False, signalling that
the extension didn't handle the request.  But the extension can return
a response containing an empty response body, and that should not be
treated the same way; such an empty response should just be returned to
the client.
Restructure "review message id" handling

Add a utility function that looks up the message id of the "New review"
mail, or, if none was sent, sends a "New(ish) review" mail (AKA "review
placeholder" mail.)  This eliminates some duplicated code, mostly.

Also reformat code to avoid some overly long lines.
In install/upgrade, avoid running git as root

Doing so occasionally leaves files owned by root under .git/, even if
none of the commands run actually modify the repository.

Implement by adding a helper installation.utils.run_git(), which switches
to the user/group that owns $(installation.root_dir)/.git.

Also simplify some context manager code using the contextlib module.
Move .install.data reading and writing to installation.utils

Also write after running installation.*.finish() to allow those to trim
the written data dictionary.  Then change the permissions on the
.install.data file so that it is owned by the Critic system user/group
and is readable by the group (like all other files under $INSTALL_DIR.)
Fix contextlib.contextmanager uses

If the controlled block of the with statement raises an exception, it is
re-raised at the point of the yield, so for the rest of the context
manager function to be executed, a try-finally must be used.
Check the returned document's content type in frontend.page()
Add script (extend.py) for enabling extensions

Also add the js/v8 flavor extension library code.
Add support for testing extensions

If the extensions/v8-jsshell sub-module is checked out, it is also
exported to the guest system (as git://host/v8-jsshell.git) thus
enabling it to be checked out there using "git submodule update".
(The extensions/v8-jsshell/v8 submodule is fetched from github.com.)

Extensions is enabled by a test (similar to how Critic is installed
and upgraded) that calls virtualbox.Instance.extend().  To improve
performance, the actual jsshell binary is cached (per instance and
extensions/v8-jsshell sub-module SHA-1) so that it doesn't need to
be built during every test run.  When a cached binary is found, the
"--fetch" and "--build" extend.py actions are skipped.

Extension testing is currently opt-in; the testing framework needs
to be started with --test-extensions for it to be enabled.  If not,
the virtualbox.Instance.extend() function raises a NotSupported
exception, which is like a TestFailed extension in that it causes
dependent tests to be skipped, but is not considered an error.
Add TestExtension and some basic extensions tests
Add support for listing reviews matching certain criteria

This adds the class function critic.Review.list(), some tests for it, and
updates the extensions/v8-jsshell sub-module to a commit that renames
PostgreSQL.Result.discard() to close(), making Result objects compatible
with scoped() (which the new code in critic-review.js makes use of.)
Add API for sending custom mails from extensions

Most of the work is done via cli.py, to avoid duplicating review
recipient list handling, and the code that adds OperaCritic-* headers.

Also add some basic tests, and tweak the testing frameworks handling of
mails, and missing or unexpected mails.
Define REMOTE_USER when pushing, and stop guessing

When the Critic system user updates a branch, the git hook would
try to guess which user was actually pushing by looking at the
commit meta data (specifically the committer email address) of the
new head of the updated branch.  This is obviously flawed in all
sorts of ways, and would cause crashes in some cases if no user
could be identified that way.

Instead of doing this, define REMOTE_USER when pushing (both via
the /rebasereview operation and via extensions) as the Critic
system user, and stop guessing in the git hook.

Also adjust the extension repository work copy mechanism to be
unavailable if the user is anonymous.  Anonymous users can only use
extensions that the system's administrator has installed
universally (which ought to mean the system administrator trusts
the extension, but since work copies are kept semi-persistently per
user, it sort of doesn't make sense to allow them for anonymous
users.
Move SMTP credentials into new file owned by root

This means the running system (or extensions running as the 'critic'
system user) can't read the file.  The contents are read by the
service manager before it drops privileges, and passed to the mail
delivery service on startup.
Line-break error mail header at 80 columns