launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #24403
[Merge] ~pappacena/turnip:python3-take3 into turnip:master
Thiago F. Pappacena has proposed merging ~pappacena/turnip:python3-take3 into turnip:master.
Commit message:
Turnip running on Python3
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
For more details, see:
https://code.launchpad.net/~pappacena/turnip/+git/turnip/+merge/380040
This is a continuation of the work done by twom and cjwatson.
Running `make check` runs smoothly on a bionic lxc, with just one deprecation warning deeply hidden on Twisted and a warning at the end saying that a `/dev/null` file descriptor was not properly closed.
--
Your team Launchpad code reviewers is requested to review the proposed merge of ~pappacena/turnip:python3-take3 into turnip:master.
diff --git a/Makefile b/Makefile
index c43f2ee..87fe97a 100644
--- a/Makefile
+++ b/Makefile
@@ -49,7 +49,7 @@ endif
echo "allow_hosts = ''"; \
echo 'find_links = file://$(realpath $(PIP_SOURCE_DIR))/') \
>$(ENV)/.pydistutils.cfg
- $(VIRTUALENV) --never-download $(ENV)
+ $(VIRTUALENV) -p python3 --never-download $(ENV)
$(PIP) install $(PIP_ARGS) -r bootstrap-requirements.txt
$(PIP) install $(PIP_ARGS) -c requirements.txt \
-e '.[test,deploy]'
diff --git a/requirements.txt b/requirements.txt
index b706f3c..7e7c073 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,13 +1,13 @@
appdirs==1.4.3
asn1crypto==0.24.0
-attrs==17.4.0
-Automat==0.6.0
-beautifulsoup4==4.3.2
-cffi==1.11.4
+attrs==19.3.0
+Automat==20.2.0
+beautifulsoup4==4.6.3
+cffi==1.14.0
constantly==15.1.0
contextlib2==0.4.0
-cornice==0.19.0
-cryptography==2.1.4
+cornice==3.6.1
+cryptography==2.4.2
docutils==0.14
enum34==1.1.6
envdir==0.7
@@ -16,8 +16,8 @@ fixtures==1.0.0
flake8==2.4.0
gmpy==1.17
gunicorn==19.3.0
-hyperlink==18.0.0
-idna==2.6
+hyperlink==19.0.0
+idna==2.9
incremental==17.5.0
ipaddress==1.0.19
lazr.sshserver==0.1.8
@@ -26,35 +26,32 @@ m2r==0.1.14
mccabe==0.3
mistune==0.8.3
Paste==2.0.2
-PasteDeploy==1.5.2
+PasteDeploy==2.1.0
pbr==1.8.1
pep8==1.5.7
-pyasn1==0.1.7
-pycparser==2.10
+pyasn1==0.4.8
+pycparser==2.19
pycrypto==2.6.1
pyflakes==0.8.1
pygit2==0.27.4
-pyramid==1.5.4
+pyramid==1.10.4
python-mimeparse==0.1.4
-# XXX: deryck 2012-08-10
-# See lp:~deryck/python-openid/python-openid-fix1034376 which
-# reapplied a patch from wgrant to get codehosting going again.
-python-openid==2.2.5-fix1034376
-PyYAML==3.11
+python3-openid==3.1.0
+PyYAML==5.3
repoze.lru==0.6
setuptools-scm==1.17.0
simplejson==3.6.5
-six==1.9.0
-testtools==1.8.1
+six==1.14.0
+testtools==2.3.0
traceback2==1.4.0
translationstring==1.3
-Twisted[conch]==18.4.0
+Twisted[conch]==18.9.0
unittest2==1.0.1
-venusian==1.0
+venusian==3.0.0
waitress==0.8.9
-WebOb==1.4
+WebOb==1.8.6
WebTest==2.0.18
zope.component==4.2.1
-zope.deprecation==4.1.2
+zope.deprecation==4.4.0
zope.event==4.0.3
-zope.interface==4.5.0
+zope.interface==4.7.1
diff --git a/setup.py b/setup.py
index cd8f4fb..28d15fa 100755
--- a/setup.py
+++ b/setup.py
@@ -24,7 +24,7 @@ requires = [
'lazr.sshserver>=0.1.7',
'Paste',
'pygit2>=0.27.4,<0.28.0',
- 'python-openid',
+ 'python3-openid',
'PyYAML',
'Twisted[conch]',
'waitress',
diff --git a/system-dependencies.txt b/system-dependencies.txt
index 845d453..8461133 100644
--- a/system-dependencies.txt
+++ b/system-dependencies.txt
@@ -1,8 +1,10 @@
build-essential
cgit
git
+libgit2-dev
libffi-dev
libgit2-27
libssl-dev
-python-dev
+python3-dev
+python-virtualenv
virtualenv
diff --git a/turnip/api/store.py b/turnip/api/store.py
index 11b4756..e84a83d 100644
--- a/turnip/api/store.py
+++ b/turnip/api/store.py
@@ -56,9 +56,21 @@ def format_commit(git_object):
raise GitError('Invalid type: object {} is not a commit.'.format(
git_object.oid.hex))
parents = [parent.hex for parent in git_object.parent_ids]
+ # XXX cjwatson 2018-11-15: A regression in pygit2 0.27.1 means that we
+ # have to decode the commit message ourselves. See:
+ # https://github.com/libgit2/pygit2/issues/839
+ if git_object.message_encoding is not None:
+ message = git_object.raw_message.decode(
+ encoding=git_object.message_encoding, errors="strict")
+ else:
+ # If the encoding is not explicit, it may not be UTF-8, so it is not
+ # safe to decode it strictly.
+ message = git_object.raw_message.decode(
+ encoding="UTF-8", errors="replace")
+
return {
'sha1': git_object.oid.hex,
- 'message': git_object.message,
+ 'message': message,
'author': format_signature(git_object.author),
'committer': format_signature(git_object.committer),
'parents': parents,
@@ -335,7 +347,17 @@ def get_refs(repo_store, repo_name, exclude_prefixes=None):
"""Return all refs for a git repository."""
with open_repo(repo_store, repo_name) as repo:
refs = {}
- for ref in repo.listall_references():
+ for ref_obj in repo.listall_reference_objects():
+ try:
+ ref = ref_obj.name
+ git_object = repo.references[ref].peel()
+ except UnicodeDecodeError:
+ pass
+ else:
+ if not any(ref.startswith(exclude_prefix)
+ for exclude_prefix in (exclude_prefixes or [])):
+ refs.update(format_ref(ref, git_object))
+ """
git_object = repo.references[ref].peel()
# Filter non-unicode refs, as refs are treated as unicode
# given json is unable to represent arbitrary byte strings.
@@ -347,6 +369,7 @@ def get_refs(repo_store, repo_name, exclude_prefixes=None):
if not any(ref.startswith(exclude_prefix)
for exclude_prefix in (exclude_prefixes or [])):
refs.update(format_ref(ref, git_object))
+ """
return refs
diff --git a/turnip/api/tests/test_api.py b/turnip/api/tests/test_api.py
index 2ff34d0..f603c09 100644
--- a/turnip/api/tests/test_api.py
+++ b/turnip/api/tests/test_api.py
@@ -55,7 +55,7 @@ class ApiTestCase(TestCase):
repo.references[observed].peel().oid)
def get_ref(self, ref):
- resp = self.app.get('/repo/{}/{}'.format(self.repo_path, ref))
+ resp = self.app.get('/repo/{}/{}'.format(self.repo_path, quote(ref)))
return resp.json
def test_repo_init(self):
@@ -217,7 +217,7 @@ class ApiTestCase(TestCase):
"""Ensure non-unicode refs are dropped from ref collection."""
factory = RepoFactory(self.repo_store)
commit_oid = factory.add_commit('foo', 'foobar.txt')
- tag = '\xe9\xe9\xe9' # latin-1
+ tag = b'\xe9\xe9\xe9' # latin-1
tag_message = 'tag message'
factory.add_tag(tag, tag_message, commit_oid)
@@ -229,8 +229,8 @@ class ApiTestCase(TestCase):
"""Ensure unicode refs are included in ref collection."""
factory = RepoFactory(self.repo_store)
commit_oid = factory.add_commit('foo', 'foobar.txt')
- tag = u'おいしいイカ'.encode('utf-8')
- tag_message = u'かわいい タコ'.encode('utf-8')
+ tag = 'おいしいイカ'
+ tag_message = 'かわいい タコ'
factory.add_tag(tag, tag_message, commit_oid)
resp = self.app.get('/repo/{}/refs'.format(self.repo_path))
@@ -281,13 +281,13 @@ class ApiTestCase(TestCase):
def test_repo_get_unicode_ref(self):
factory = RepoFactory(self.repo_store)
commit_oid = factory.add_commit('foo', 'foobar.txt')
- tag_name = u'☃'.encode('utf-8')
- tag_message = u'☃'.encode('utf-8')
+ tag_name = '☃'
+ tag_message = '☃'
factory.add_tag(tag_name, tag_message, commit_oid)
tag = 'refs/tags/{}'.format(tag_name)
resp = self.get_ref(tag)
- self.assertTrue(tag.decode('utf-8') in resp)
+ self.assertTrue(tag in resp)
def test_repo_get_tag(self):
RepoFactory(self.repo_store, num_commits=1, num_tags=1).build()
@@ -335,7 +335,7 @@ class ApiTestCase(TestCase):
def test_repo_diff_non_unicode_commits(self):
"""Ensure non utf-8 chars are handled but stripped from diff."""
factory = RepoFactory(self.repo_store)
- message = 'not particularly sensible latin-1: \xe9\xe9\xe9.'
+ message = b'not particularly sensible latin-1: \xe9\xe9\xe9.'
oid = factory.add_commit(message, 'foo.py')
oid2 = factory.add_commit('a sensible commit message', 'foo.py', [oid])
@@ -406,7 +406,7 @@ class ApiTestCase(TestCase):
self.repo_path, quote('{}^'.format(c2)), c2)
resp = self.app.get(path)
self.assertIn(
- b'diff --git a/foo.txt b/bar.txt\n', resp.json_body['patch'])
+ 'diff --git a/foo.txt b/bar.txt\n', resp.json_body['patch'])
def test_repo_diff_rename_and_change_content_conflict(self):
# Create repo1 with foo.txt.
@@ -576,7 +576,7 @@ class ApiTestCase(TestCase):
resp = self.app.get('/repo/{}/compare-merge/{}:{}'.format(
self.repo_path, c1, c2))
self.assertIn(
- b'diff --git a/foo.txt b/bar.txt\n', resp.json_body['patch'])
+ 'diff --git a/foo.txt b/bar.txt\n', resp.json_body['patch'])
def test_repo_get_commit(self):
factory = RepoFactory(self.repo_store)
@@ -636,12 +636,12 @@ class ApiTestCase(TestCase):
def test_repo_get_log_signatures(self):
"""Ensure signatures are correct."""
factory = RepoFactory(self.repo_store)
- committer = factory.makeSignature(u'村上 春樹'.encode('utf-8'),
- u'tsukuru@猫の町.co.jp'.encode('utf-8'),
+ committer = factory.makeSignature('村上 春樹'.encode('utf-8'),
+ 'tsukuru@猫の町.co.jp',
encoding='utf-8')
author = factory.makeSignature(
- u'Владимир Владимирович Набоков'.encode('utf-8'),
- u'Набоко@zembla.ru'.encode('utf-8'), encoding='utf-8')
+ 'Владимир Владимирович Набоков'.encode('utf-8'),
+ 'Набоко@zembla.ru', encoding='utf-8')
oid = factory.add_commit('Obfuscate colophon.', 'path.foo',
author=author, committer=committer)
resp = self.app.get('/repo/{}/log/{}'.format(self.repo_path, oid))
@@ -671,7 +671,7 @@ class ApiTestCase(TestCase):
def test_repo_get_non_unicode_log(self):
"""Ensure that non-unicode data is discarded."""
factory = RepoFactory(self.repo_store)
- message = '\xe9\xe9\xe9' # latin-1
+ message = b'\xe9\xe9\xe9' # latin-1
oid = factory.add_commit(message, 'foo.py')
resp = self.app.get('/repo/{}/log/{}'.format(self.repo_path, oid))
self.assertEqual(message.decode('utf-8', 'replace'),
@@ -809,15 +809,15 @@ class ApiTestCase(TestCase):
factory.add_commit('b\n', 'dir/file', parents=[c1])
resp = self.app.get('/repo/{}/blob/dir/file'.format(self.repo_path))
self.assertEqual(2, resp.json['size'])
- self.assertEqual('b\n', base64.b64decode(resp.json['data']))
+ self.assertEqual(b'b\n', base64.b64decode(resp.json['data']))
resp = self.app.get('/repo/{}/blob/dir/file?rev=master'.format(
self.repo_path))
self.assertEqual(2, resp.json['size'])
- self.assertEqual('b\n', base64.b64decode(resp.json['data']))
+ self.assertEqual(b'b\n', base64.b64decode(resp.json['data']))
resp = self.app.get('/repo/{}/blob/dir/file?rev={}'.format(
self.repo_path, c1.hex))
self.assertEqual(2, resp.json['size'])
- self.assertEqual('a\n', base64.b64decode(resp.json['data']))
+ self.assertEqual(b'a\n', base64.b64decode(resp.json['data']))
def test_repo_blob_missing_commit(self):
"""Trying to get a blob from a non-existent commit returns HTTP 404."""
@@ -861,7 +861,7 @@ class ApiTestCase(TestCase):
resp = self.app.get('/repo/{}/blob/dir/file?rev=tag-name'.format(
self.repo_path))
self.assertEqual(2, resp.json['size'])
- self.assertEqual('a\n', base64.b64decode(resp.json['data']))
+ self.assertEqual(b'a\n', base64.b64decode(resp.json['data']))
def test_repo_blob_from_non_commit(self):
"""Trying to get a blob from a non-commit returns HTTP 404."""
diff --git a/turnip/api/tests/test_helpers.py b/turnip/api/tests/test_helpers.py
index dac6950..0ca1551 100644
--- a/turnip/api/tests/test_helpers.py
+++ b/turnip/api/tests/test_helpers.py
@@ -5,6 +5,7 @@ import contextlib
import fnmatch
import itertools
import os
+import subprocess
import uuid
from six.moves import urllib
@@ -13,7 +14,6 @@ from pygit2 import (
clone_repository,
init_repository,
GIT_FILEMODE_BLOB,
- GIT_OBJ_COMMIT,
IndexEntry,
Repository,
Signature,
@@ -96,9 +96,9 @@ class RepoFactory(object):
def add_tag(self, tag_name, tag_message, oid):
"""Create a tag from tag_name and oid."""
- repo = self.repo
- repo.create_tag(tag_name, oid, GIT_OBJ_COMMIT,
- self.committer, tag_message)
+ subprocess.check_call(
+ ['git', '-C', self.repo_path,
+ 'tag', '-m', tag_message, tag_name, oid.hex])
def makeSignature(self, name, email, encoding='utf-8'):
"""Return an author or committer signature."""
diff --git a/turnip/api/tests/test_store.py b/turnip/api/tests/test_store.py
index f035eca..c1a4803 100644
--- a/turnip/api/tests/test_store.py
+++ b/turnip/api/tests/test_store.py
@@ -118,7 +118,7 @@ class InitTestCase(TestCase):
store.init_repo(repo_path)
repo_config = pygit2.Repository(repo_path).config
with open('git.config.yaml') as f:
- yaml_config = yaml.load(f)
+ yaml_config = yaml.load(f, Loader=yaml.SafeLoader)
self.assertEqual(bool(yaml_config['core.logallrefupdates']),
bool(repo_config['core.logallrefupdates']))
diff --git a/turnip/api/views.py b/turnip/api/views.py
index adede04..95640ca 100644
--- a/turnip/api/views.py
+++ b/turnip/api/views.py
@@ -6,7 +6,7 @@ import re
from subprocess import CalledProcessError
from cornice.resource import resource
-from cornice.util import extract_json_data
+from cornice.validators import extract_cstruct
from pygit2 import GitError
import pyramid.httpexceptions as exc
@@ -14,6 +14,10 @@ from turnip.config import config
from turnip.api import store
+def extract_json_data(request):
+ return extract_cstruct(request)['body']
+
+
def is_valid_path(repo_store, repo_path):
"""Ensure path in within repo root and has not been subverted."""
return os.path.realpath(repo_path).startswith(os.path.realpath(repo_store))
@@ -43,7 +47,7 @@ class BaseAPI(object):
class RepoAPI(BaseAPI):
"""Provides HTTP API for repository actions."""
- def __init__(self, request):
+ def __init__(self, request, context=None):
super(RepoAPI, self).__init__()
self.request = request
@@ -127,7 +131,7 @@ class RepoAPI(BaseAPI):
class RepackAPI(BaseAPI):
"""Provides HTTP API for repository repacking."""
- def __init__(self, request):
+ def __init__(self, request, context=None):
super(RepackAPI, self).__init__()
self.request = request
@@ -158,7 +162,7 @@ class RepackAPI(BaseAPI):
class RefAPI(BaseAPI):
"""Provides HTTP API for git references."""
- def __init__(self, request):
+ def __init__(self, request, context=None):
super(RefAPI, self).__init__()
self.request = request
@@ -190,14 +194,14 @@ class DiffAPI(BaseAPI):
to `git diff $(git-merge-base A B) B`.
{name} can be two : separated repositories, for a cross repository diff.
"""
- def __init__(self, request):
+ def __init__(self, request, context=None):
super(DiffAPI, self).__init__()
self.request = request
@validate_path
def get(self, repo_store, repo_name):
"""Returns diff of two commits."""
- commits = re.split('(\.{2,3})', self.request.matchdict['commits'])
+ commits = re.split(r'(\.{2,3})', self.request.matchdict['commits'])
context_lines = int(self.request.params.get('context_lines', 3))
if not len(commits) == 3:
return exc.HTTPBadRequest()
@@ -222,7 +226,7 @@ class DiffMergeAPI(BaseAPI):
{head} will be merged into {base} and the diff from {base} returned.
{name} can be two : separated repositories, for a cross repository diff.
"""
- def __init__(self, request):
+ def __init__(self, request, context=None):
super(DiffMergeAPI, self).__init__()
self.request = request
@@ -247,7 +251,7 @@ class DiffMergeAPI(BaseAPI):
class CommitAPI(BaseAPI):
"""Provides HTTP API for git commits."""
- def __init__(self, request):
+ def __init__(self, request, context=None):
super(CommitAPI, self).__init__()
self.request = request
@@ -275,7 +279,7 @@ class CommitAPI(BaseAPI):
class LogAPI(BaseAPI):
"""Provides HTTP API for git logs."""
- def __init__(self, request):
+ def __init__(self, request, context=None):
super(LogAPI, self).__init__()
self.request = request
@@ -297,7 +301,7 @@ class LogAPI(BaseAPI):
class DetectMergesAPI(BaseAPI):
"""Provides HTTP API for detecting merges."""
- def __init__(self, request):
+ def __init__(self, request, context=None):
super(DetectMergesAPI, self).__init__()
self.request = request
@@ -326,7 +330,7 @@ class DetectMergesAPI(BaseAPI):
class BlobAPI(BaseAPI):
"""Provides HTTP API for fetching blobs."""
- def __init__(self, request):
+ def __init__(self, request, context=None):
super(BlobAPI, self).__init__()
self.request = request
diff --git a/turnip/helpers.py b/turnip/helpers.py
index a423aff..1ea5298 100644
--- a/turnip/helpers.py
+++ b/turnip/helpers.py
@@ -14,7 +14,7 @@ def compose_path(root, path):
# Construct the full path, stripping any leading slashes so we
# resolve absolute paths within the root.
full_path = os.path.abspath(os.path.join(
- root, path.lstrip(os.path.sep.encode('utf-8'))))
+ root, path.lstrip(os.path.sep)))
if not full_path.startswith(os.path.abspath(root)):
raise ValueError('Path not contained within root')
return full_path
diff --git a/turnip/pack/git.py b/turnip/pack/git.py
index 762be91..f255e84 100644
--- a/turnip/pack/git.py
+++ b/turnip/pack/git.py
@@ -36,7 +36,7 @@ from turnip.pack.helpers import (
ERROR_PREFIX = b'ERR '
VIRT_ERROR_PREFIX = b'turnip virt error: '
-SAFE_PARAMS = frozenset(['host'])
+SAFE_PARAMS = frozenset([b'host'])
class RequestIDLogger(Logger):
@@ -410,7 +410,7 @@ class PackBackendProtocol(PackServerProtocol):
self.extractRequestMeta(command, raw_pathname, params)
self.command = command
self.raw_pathname = raw_pathname
- self.path = compose_path(self.factory.root, self.raw_pathname)
+ self.path = compose_path(self.factory.root, self.raw_pathname.decode())
if command == b'turnip-set-symbolic-ref':
self.expect_set_symbolic_ref = True
@@ -560,7 +560,7 @@ class PackVirtServerProtocol(PackProxyServerProtocol):
auth_params = self.createAuthParams(params)
self.log.info("Translating request.")
translated = yield proxy.callRemote(
- b'translatePath', pathname, permission,
+ 'translatePath', pathname, permission,
auth_params).addTimeout(
self.factory.virtinfo_timeout, self.factory.reactor)
self.log.info(
@@ -569,7 +569,7 @@ class PackVirtServerProtocol(PackProxyServerProtocol):
self.die(
VIRT_ERROR_PREFIX +
b'NOT_FOUND Repository does not exist.')
- pathname = translated['path']
+ pathname = translated['path'].encode('utf-8')
except xmlrpc.Fault as e:
fault_type = translate_xmlrpc_fault(
e.faultCode).name.encode('UTF-8')
@@ -639,7 +639,7 @@ class PackFrontendServerProtocol(PackProxyServerProtocol):
if set(params.keys()) - SAFE_PARAMS:
self.die(b'Illegal request parameters')
return
- params[b'turnip-request-id'] = self.request_id
+ params[b'turnip-request-id'] = self.request_id.encode('utf-8')
self.connectToBackend(command, pathname, params)
diff --git a/turnip/pack/helpers.py b/turnip/pack/helpers.py
index 43faa6e..95e0632 100644
--- a/turnip/pack/helpers.py
+++ b/turnip/pack/helpers.py
@@ -111,7 +111,7 @@ def ensure_config(repo_root):
about concurrency.
"""
with open('git.config.yaml') as config_file:
- git_config_defaults = yaml.load(config_file)
+ git_config_defaults = yaml.load(config_file, Loader=yaml.SafeLoader)
config = Repository(repo_root).config
for key, val in git_config_defaults.items():
config[key] = val
diff --git a/turnip/pack/hookrpc.py b/turnip/pack/hookrpc.py
index 91a03a4..c5b0d68 100644
--- a/turnip/pack/hookrpc.py
+++ b/turnip/pack/hookrpc.py
@@ -168,7 +168,7 @@ class HookRPCHandler(object):
proxy = xmlrpc.Proxy(self.virtinfo_url, allowNone=True)
try:
result = yield proxy.callRemote(
- b'checkRefPermissions',
+ 'checkRefPermissions',
ref_path,
[xmlrpc_client.Binary(path) for path in missing],
auth_params).addTimeout(
@@ -228,7 +228,7 @@ class HookRPCHandler(object):
@defer.inlineCallbacks
def notify(self, path):
proxy = xmlrpc.Proxy(self.virtinfo_url, allowNone=True)
- yield proxy.callRemote(b'notify', path).addTimeout(
+ yield proxy.callRemote('notify', path).addTimeout(
self.virtinfo_timeout, self.reactor)
@defer.inlineCallbacks
diff --git a/turnip/pack/hooks/hook.py b/turnip/pack/hooks/hook.py
index 30189b1..1dd4cb4 100755
--- a/turnip/pack/hooks/hook.py
+++ b/turnip/pack/hooks/hook.py
@@ -16,15 +16,22 @@ import socket
import subprocess
import sys
+from pygit2 import GIT_OID_HEX_ZERO
-# XXX twom 2018-10-23 This should be a pygit2 import, but
-# that currently causes CFFI warnings to be returned to the client.
-GIT_OID_HEX_ZERO = '0'*40
+
+def is_git_oid_hex_zero(value):
+ """Checks if the given value is the git initial hash (either str or bytes).
+
+ See pygit2.GIT_OID_HEX_ZERO constant for details.
+ """
+ if isinstance(value, bytes):
+ value = value.decode('utf8')
+ return value == GIT_OID_HEX_ZERO
def check_ancestor(old, new):
# This is a delete, setting the new ref.
- if new == GIT_OID_HEX_ZERO:
+ if is_git_oid_hex_zero(new):
return False
# https://git-scm.com/docs/git-merge-base#_discussion
return_code = subprocess.call(
@@ -34,7 +41,7 @@ def check_ancestor(old, new):
def determine_permissions_outcome(old, ref, rule_lines):
rule = rule_lines.get(ref, [])
- if old == GIT_OID_HEX_ZERO:
+ if is_git_oid_hex_zero(old):
# We are creating a new ref
if 'create' in rule:
return
@@ -79,7 +86,7 @@ def match_update_rules(rule_lines, ref_line):
ref, old, new = ref_line
# If it's a create, the old ref doesn't exist
- if old == GIT_OID_HEX_ZERO:
+ if is_git_oid_hex_zero(old):
return []
# https://git-scm.com/docs/git-merge-base#_discussion
@@ -94,12 +101,12 @@ def match_update_rules(rule_lines, ref_line):
def netstring_send(sock, s):
- sock.send(b'%d:%s,' % (len(s), s))
+ sock.send(b'%d:%s,' % (len(s), s.encode('utf-8')))
def netstring_recv(sock):
c = sock.recv(1)
- lengthstr = ''
+ lengthstr = b''
while c != b':':
assert c.isdigit()
lengthstr += c
@@ -126,7 +133,7 @@ def rpc_invoke(sock, method, args):
def check_ref_permissions(sock, rpc_key, ref_paths):
ref_paths = [base64.b64encode(path).decode('UTF-8') for path in ref_paths]
rule_lines = rpc_invoke(
- sock, b'check_ref_permissions',
+ sock, 'check_ref_permissions',
{'key': rpc_key, 'paths': ref_paths})
return {
base64.b64decode(path.encode('UTF-8')): permissions
@@ -143,25 +150,27 @@ if __name__ == '__main__':
hook = os.path.basename(sys.argv[0])
if hook == 'pre-receive':
# Verify the proposed changes against rules from the server.
- raw_paths = sys.stdin.readlines()
+ raw_paths = sys.stdin.buffer.readlines()
ref_paths = [p.rstrip(b'\n').split(b' ', 2)[2] for p in raw_paths]
rule_lines = check_ref_permissions(sock, rpc_key, ref_paths)
errors = match_rules(rule_lines, raw_paths)
for error in errors:
- sys.stdout.write(error + b'\n')
+ sys.stdout.buffer.write(error + b'\n')
sys.exit(1 if errors else 0)
elif hook == 'post-receive':
# Notify the server about the push if there were any changes.
# Details of the changes aren't currently included.
- if sys.stdin.readlines():
- rpc_invoke(sock, b'notify_push', {'key': rpc_key})
+ if sys.stdin.buffer.readlines():
+ rpc_invoke(sock, 'notify_push', {'key': rpc_key})
sys.exit(0)
elif hook == 'update':
- ref = sys.argv[1]
+ # https://stackoverflow.com/a/27185688
+ argvb = list(map(os.fsencode, sys.argv))
+ ref = argvb[1]
rule_lines = check_ref_permissions(sock, rpc_key, [ref])
- errors = match_update_rules(rule_lines, sys.argv[1:4])
+ errors = match_update_rules(rule_lines, argvb[1:4])
for error in errors:
- sys.stdout.write(error + b'\n')
+ sys.stdout.buffer.write(error + b'\n')
sys.exit(1 if errors else 0)
else:
sys.stderr.write('Invalid hook name: %s' % hook)
diff --git a/turnip/pack/http.py b/turnip/pack/http.py
index 573a37d..af2cd58 100644
--- a/turnip/pack/http.py
+++ b/turnip/pack/http.py
@@ -281,12 +281,12 @@ class BaseSmartHTTPResource(resource.Resource):
"""
params = {
b'turnip-can-authenticate': b'yes',
- b'turnip-request-id': str(uuid.uuid4()),
+ b'turnip-request-id': str(uuid.uuid4()).encode('utf-8'),
}
authenticated_params = yield self.authenticateUser(request)
for key, value in authenticated_params.items():
encoded_key = ('turnip-authenticated-' + key).encode('utf-8')
- params[encoded_key] = unicode(value).encode('utf-8')
+ params[encoded_key] = str(value).encode('utf-8')
params.update(self.extra_params)
d = defer.Deferred()
client_factory = factory(service, path, params, content, request, d)
@@ -309,7 +309,7 @@ class SmartHTTPRefsResource(BaseSmartHTTPResource):
def render_GET(self, request):
try:
- service = request.args['service'][0]
+ service = request.args[b'service'][0]
except (KeyError, IndexError):
return fail_request(
request, b'Only git smart HTTP clients are supported.',
@@ -660,7 +660,7 @@ class HTTPAuthRootResource(BaseHTTPAuthResource):
identity_url = session.get('identity_url', self.anonymous_id)
proxy = xmlrpc.Proxy(self.root.virtinfo_endpoint, allowNone=True)
d = proxy.callRemote(
- b'translatePath', request.path, b'read',
+ 'translatePath', request.path, b'read',
{'uid': identity_url, 'can-authenticate': True})
d.addTimeout(self.root.virtinfo_timeout, self.root.reactor)
d.addCallback(self._translatePathCallback, request)
@@ -742,8 +742,8 @@ class SmartHTTPFrontendResource(resource.Resource):
@staticmethod
def _isGitRequest(request):
if request.path.endswith(b'/info/refs'):
- service = request.args.get('service', [])
- if service and service[0].startswith('git-'):
+ service = request.args.get(b'service', [])
+ if service and service[0].startswith(b'git-'):
return True
content_type = request.getHeader(b'Content-Type')
if content_type is None:
@@ -782,7 +782,7 @@ class SmartHTTPFrontendResource(resource.Resource):
proxy = xmlrpc.Proxy(self.virtinfo_endpoint)
try:
translated = yield proxy.callRemote(
- b'authenticateWithPassword', user, password)
+ 'authenticateWithPassword', user, password)
except xmlrpc.Fault as e:
code = translate_xmlrpc_fault(e.faultCode)
if code == TurnipFaultCode.UNAUTHORIZED:
diff --git a/turnip/pack/ssh.py b/turnip/pack/ssh.py
index 3526bb9..87a7794 100644
--- a/turnip/pack/ssh.py
+++ b/turnip/pack/ssh.py
@@ -136,8 +136,9 @@ class SmartSSHSession(DoNothingSession):
"""
params = {
b'turnip-authenticated-user': self.avatar.username.encode('utf-8'),
- b'turnip-authenticated-uid': str(self.avatar.user_id),
- b'turnip-request-id': str(uuid.uuid4()),
+ b'turnip-authenticated-uid': str(
+ self.avatar.user_id).encode('utf-8'),
+ b'turnip-request-id': str(uuid.uuid4()).encode('utf-8'),
}
d = defer.Deferred()
client_factory = factory(service, path, params, ssh_protocol, d)
@@ -149,7 +150,9 @@ class SmartSSHSession(DoNothingSession):
def execCommand(self, protocol, command):
"""See `ISession`."""
- words = shlex.split(command)
+ # shlex doesn't operate on, nor return bytes.
+ words = shlex.split(command.decode())
+ words = [x.encode('utf-8') for x in words]
if len(words) > 1 and words[0] == "git":
# Accept "git foo" as if the caller said "git-foo". (This
# matches the behaviour of "git shell".)
diff --git a/turnip/pack/tests/fake_servers.py b/turnip/pack/tests/fake_servers.py
index 0914637..2fb39d6 100644
--- a/turnip/pack/tests/fake_servers.py
+++ b/turnip/pack/tests/fake_servers.py
@@ -40,6 +40,7 @@ class FakeAuthServerService(xmlrpc.XMLRPC):
self.keys[username].append((keytype, keytext))
def xmlrpc_getUserAndSSHKeys(self, username):
+ username = str(username)
if username not in self.keys:
raise NoSuchPersonWithName(username)
return {
@@ -67,10 +68,13 @@ class FakeVirtInfoService(xmlrpc.XMLRPC):
self.ref_permissions_fault = None
def xmlrpc_translatePath(self, pathname, permission, auth_params):
+ pathname = str(pathname)
if self.require_auth and 'user' not in auth_params:
raise xmlrpc.Fault(3, "Unauthorized")
- self.translations.append((pathname, permission, auth_params))
+ if 'user' in auth_params:
+ auth_params['user'] = str(auth_params['user'])
+ self.translations.append((pathname, str(permission), auth_params))
writable = False
if pathname.startswith('/+rw'):
writable = True
@@ -78,14 +82,14 @@ class FakeVirtInfoService(xmlrpc.XMLRPC):
if permission != b'read' and not writable:
raise xmlrpc.Fault(2, "Repository is read-only")
- return {'path': hashlib.sha256(pathname).hexdigest()}
+ return {'path': hashlib.sha256(pathname.encode('utf-8')).hexdigest()}
def xmlrpc_authenticateWithPassword(self, username, password):
- self.authentications.append((username, password))
+ self.authentications.append((str(username), str(password)))
return {'user': username}
def xmlrpc_notify(self, path):
- self.push_notifications.append(path)
+ self.push_notifications.append(str(path))
def xmlrpc_checkRefPermissions(self, path, ref_paths, auth_params):
self.ref_permissions_checks.append((path, ref_paths, auth_params))
diff --git a/turnip/pack/tests/test_functional.py b/turnip/pack/tests/test_functional.py
index 8927b6f..4dc00fb 100644
--- a/turnip/pack/tests/test_functional.py
+++ b/turnip/pack/tests/test_functional.py
@@ -16,6 +16,7 @@ import random
import shutil
import stat
import tempfile
+
try:
from urllib.parse import (
urlsplit,
@@ -32,14 +33,18 @@ from fixtures import (
TempDir,
)
from pygit2 import GIT_OID_HEX_ZERO
+from six.moves import xmlrpc_client
from testtools import TestCase
from testtools.content import text_content
from testtools.deferredruntest import AsynchronousDeferredRunTest
from testtools.matchers import (
Equals,
Is,
+ IsInstance,
+ MatchesAll,
MatchesDict,
MatchesListwise,
+ MatchesStructure,
Not,
StartsWith,
)
@@ -48,12 +53,18 @@ from twisted.internet import (
reactor,
utils,
)
+from twisted.internet.protocol import Protocol
from twisted.web import (
client,
http_headers,
server,
xmlrpc,
)
+from twisted.web.client import (
+ ResponseDone,
+ PartialDownloadError,
+ )
+from twisted.web.http import PotentialDataLoss
from turnip.pack import helpers
from turnip.pack.git import (
@@ -74,6 +85,38 @@ from turnip.pack.tests.fake_servers import (
from turnip.version_info import version_info
+def readBody(response):
+ """Reads the content of the given response.
+
+ XXX: pappacena 2020-02-26: This function is mostly replicating
+ twisted.web.client.readBody, except that it doesn't raise
+ DeprecationWarning due to a strange behavior on protocol.transport not
+ having abortConnection method.
+ """
+ class ReadBodyProtocol(Protocol):
+ def __init__(self, deferred):
+ self.deferred = deferred
+ self.dataBuffer = []
+
+ def dataReceived(self, data):
+ self.dataBuffer.append(data)
+
+ def connectionLost(self, reason):
+ if reason.check(ResponseDone):
+ self.deferred.callback(b''.join(self.dataBuffer))
+ elif reason.check(PotentialDataLoss):
+ self.deferred.errback(
+ PartialDownloadError(self.status, self.message,
+ b''.join(self.dataBuffer)))
+ else:
+ self.deferred.errback(reason)
+
+ d = defer.Deferred()
+ protocol = ReadBodyProtocol(d)
+ response.deliverBody(protocol)
+ return d
+
+
class FunctionalTestMixin(object):
run_tests_with = AsynchronousDeferredRunTest.make_factory(timeout=30)
@@ -92,20 +135,15 @@ class FunctionalTestMixin(object):
def startHookRPC(self):
self.hookrpc_handler = HookRPCHandler(self.virtinfo_url, 15)
- # XXX cjwatson 2018-11-20: Use bytes so that shutil.rmtree doesn't
- # get confused on Python 2.
- dir = tempfile.mkdtemp(prefix=b'turnip-test-hook-')
+ dir = tempfile.mkdtemp(prefix='turnip-test-hook-')
self.addCleanup(shutil.rmtree, dir, ignore_errors=True)
-
self.hookrpc_sock_path = os.path.join(dir, 'hookrpc_sock')
self.hookrpc_listener = reactor.listenUNIX(
self.hookrpc_sock_path, HookRPCServerFactory(self.hookrpc_handler))
self.addCleanup(self.hookrpc_listener.stopListening)
def startPackBackend(self):
- # XXX cjwatson 2018-11-20: Use bytes so that shutil.rmtree doesn't
- # get confused on Python 2.
- self.root = tempfile.mkdtemp(prefix=b'turnip-test-root-')
+ self.root = tempfile.mkdtemp(prefix='turnip-test-root-')
self.addCleanup(shutil.rmtree, self.root, ignore_errors=True)
self.backend_listener = reactor.listenTCP(
0,
@@ -119,8 +157,10 @@ class FunctionalTestMixin(object):
out, err, code = yield utils.getProcessOutputAndValue(
command[0], command[1:], env=os.environ, path=path)
if code != 0:
- self.addDetail('stdout', text_content(out))
- self.addDetail('stderr', text_content(err))
+ self.addDetail(
+ 'stdout', text_content(out.decode('unicode_escape')))
+ self.addDetail(
+ 'stderr', text_content(err.decode('unicode_escape')))
self.assertEqual(0, code)
defer.returnValue(out)
@@ -129,8 +169,10 @@ class FunctionalTestMixin(object):
out, err, code = yield utils.getProcessOutputAndValue(
command[0], command[1:], env=os.environ, path=path)
if code == 0:
- self.addDetail('stdout', text_content(out))
- self.addDetail('stderr', text_content(err))
+ self.addDetail(
+ 'stdout', text_content(out.decode('unicode_escape')))
+ self.addDetail(
+ 'stderr', text_content(err.decode('unicode_escape')))
self.assertNotEqual(0, code)
defer.returnValue((out, err))
@@ -373,8 +415,8 @@ class FunctionalTestMixin(object):
path=clone1)
with open(os.path.join(clone1, 'bigfile'), 'w') as bigfile:
# Use random contents to defeat compression.
- bigfile.write(bytearray(
- random.getrandbits(8) for _ in range(1024 * 1024)))
+ bigfile.write(
+ str(random.getrandbits(8) for _ in range(1024 * 1024)))
yield self.assertCommandSuccess(
(b'git', b'add', b'bigfile'), path=clone1)
yield self.assertCommandSuccess(
@@ -625,7 +667,9 @@ class TestSmartHTTPFrontendFunctional(FrontendFunctionalTestMixin, TestCase):
"virtinfo_endpoint": self.virtinfo_url,
"virtinfo_timeout": "15",
"repo_store": self.root,
+ "main_site_root": b"/"
}))
+
self.frontend_listener = reactor.listenTCP(0, frontend_site)
self.port = self.frontend_listener.getHost().port
@@ -644,7 +688,7 @@ class TestSmartHTTPFrontendFunctional(FrontendFunctionalTestMixin, TestCase):
b'HEAD', b'http://localhost:%d/' % self.port)
self.assertEqual(302, response.code)
self.assertEqual(
- [version_info['revision_id']],
+ [version_info['revision_id'].encode('utf-8')],
response.headers.getRawHeaders(b'X-Turnip-Revision'))
def make_set_symbolic_ref_request(self, line):
@@ -682,8 +726,8 @@ class TestSmartHTTPFrontendFunctional(FrontendFunctionalTestMixin, TestCase):
response = yield self.make_set_symbolic_ref_request(
b'HEAD refs/heads/new-head')
self.assertEqual(200, response.code)
- body = yield client.readBody(response)
- self.assertEqual((b'ACK HEAD\n', ''), helpers.decode_packet(body))
+ body = yield readBody(response)
+ self.assertEqual((b'ACK HEAD\n', b''), helpers.decode_packet(body))
head_target = yield self.get_symbolic_ref(repo, b'HEAD')
self.assertEqual(b'refs/heads/new-head', head_target)
self.assertEqual(
@@ -698,9 +742,9 @@ class TestSmartHTTPFrontendFunctional(FrontendFunctionalTestMixin, TestCase):
# This is a little weird since an error occurred, but it's
# consistent with other HTTP pack protocol responses.
self.assertEqual(200, response.code)
- body = yield client.readBody(response)
+ body = yield readBody(response)
self.assertEqual(
- (b'ERR Symbolic ref target may not start with "-"\n', ''),
+ (b'ERR Symbolic ref target may not start with "-"\n', b''),
helpers.decode_packet(body))
head_target = yield self.get_symbolic_ref(repo, b'HEAD')
self.assertEqual(b'refs/heads/master', head_target)
@@ -725,14 +769,14 @@ class TestSmartHTTPFrontendWithAuthFunctional(TestSmartHTTPFrontendFunctional):
clone = os.path.join(test_root, 'clone')
yield self.assertCommandSuccess((b'git', b'clone', self.ro_url, clone))
self.assertEqual(
- [(b'test-user', b'test-password')], self.virtinfo.authentications)
+ [('test-user', 'test-password')], self.virtinfo.authentications)
self.assertThat(self.virtinfo.translations, MatchesListwise([
MatchesListwise([
- Equals(b'/test'), Equals(b'read'),
+ Equals('/test'), Equals('read'),
MatchesDict({
- b'can-authenticate': Is(True),
- b'request-id': Not(Is(None)),
- b'user': Equals(b'test-user'),
+ 'can-authenticate': Is(True),
+ 'request-id': Not(Is(None)),
+ 'user': Equals('test-user'),
})])]))
@defer.inlineCallbacks
@@ -752,13 +796,26 @@ class TestSmartHTTPFrontendWithAuthFunctional(TestSmartHTTPFrontendFunctional):
(b'git', b'push', b'origin', b'master'), path=clone)
self.assertThat(self.virtinfo.ref_permissions_checks, MatchesListwise([
MatchesListwise([
- Equals(self.internal_name),
- Equals([b'refs/heads/master']),
- MatchesDict({
- b'can-authenticate': Is(True),
- b'request-id': Not(Is(None)),
- b'user': Equals(b'test-user'),
- })])]))
+ MatchesAll(
+ IsInstance(xmlrpc_client.Binary),
+ MatchesStructure.byEquality(
+ data=self.internal_name.encode('utf-8'))),
+ MatchesListwise([
+ MatchesAll(
+ IsInstance(xmlrpc_client.Binary),
+ MatchesStructure.byEquality(data=ref_path))
+ for ref_path in [b'refs/heads/master']
+ ]),
+ MatchesDict(
+ {
+ 'can-authenticate': Equals(True),
+ 'user': MatchesAll(
+ IsInstance(xmlrpc_client.Binary),
+ MatchesStructure.byEquality(data=b'test-user')),
+ 'request-id': IsInstance(xmlrpc_client.Binary),
+ }),
+ ]),
+ ]))
class TestSmartSSHServiceFunctional(FrontendFunctionalTestMixin, TestCase):
@@ -806,7 +863,7 @@ class TestSmartSSHServiceFunctional(FrontendFunctionalTestMixin, TestCase):
private_key_path=private_host_key, public_key_path=public_host_key,
main_log="turnip", access_log="turnip.access",
access_log_path=os.path.join(self.root, "access.log"),
- strport=b'tcp:0', moduli_path="/etc/ssh/moduli")
+ strport='tcp:0', moduli_path="/etc/ssh/moduli")
self.service.startService()
self.addCleanup(self.service.stopService)
socket = self.service.service._waitingForPort.result.socket
diff --git a/turnip/pack/tests/test_git.py b/turnip/pack/tests/test_git.py
index 42ffb1b..8d3053f 100644
--- a/turnip/pack/tests/test_git.py
+++ b/turnip/pack/tests/test_git.py
@@ -144,7 +144,7 @@ class TestPackBackendProtocol(TestCase):
b'git-upload-pack', b'/foo.git', {b'host': b'example.com'})
self.assertEqual(
(b'git',
- [b'git', b'upload-pack', os.path.join(self.root, b'foo.git')],
+ [b'git', b'upload-pack', os.path.join(self.root, 'foo.git')],
{}),
self.proto.test_process)
@@ -158,7 +158,7 @@ class TestPackBackendProtocol(TestCase):
self.assertThat(
self.proto.test_process, MatchesListwise([
Equals(b'git'),
- Equals([b'git', b'receive-pack', repo_dir.encode('utf-8')]),
+ Equals([b'git', b'receive-pack', repo_dir]),
ContainsDict(
{b'TURNIP_HOOK_RPC_SOCK': Equals(self.hookrpc_sock)})]))
@@ -175,7 +175,7 @@ class TestPackBackendProtocol(TestCase):
self.proto.test_process, MatchesListwise([
Equals(b'git'),
Equals([
- b'git', b'-C', repo_dir.encode('utf-8'), b'symbolic-ref',
+ b'git', b'-C', repo_dir, b'symbolic-ref',
b'HEAD', b'refs/heads/master']),
ContainsDict(
{b'TURNIP_HOOK_RPC_SOCK': Equals(self.hookrpc_sock)})]))
diff --git a/turnip/pack/tests/test_hooks.py b/turnip/pack/tests/test_hooks.py
index ce92c3a..aa0ad70 100644
--- a/turnip/pack/tests/test_hooks.py
+++ b/turnip/pack/tests/test_hooks.py
@@ -211,7 +211,7 @@ class TestPreReceiveHook(HookTestMixin, TestCase):
# An invalid ref is rejected.
yield self.assertRejected(
[(b'refs/heads/verboten', self.old_sha1, self.new_sha1)],
- {'refs/heads/verboten': []},
+ {b'refs/heads/verboten': []},
b"You do not have permission to push to refs/heads/verboten.\n")
@defer.inlineCallbacks
@@ -221,9 +221,9 @@ class TestPreReceiveHook(HookTestMixin, TestCase):
[(b'refs/heads/verboten', self.old_sha1, self.new_sha1),
(b'refs/heads/master', self.old_sha1, self.new_sha1),
(b'refs/heads/super-verboten', self.old_sha1, self.new_sha1)],
- {'refs/heads/verboten': [],
- 'refs/heads/super-verboten': [],
- 'refs/heads/master': ['push']},
+ {b'refs/heads/verboten': [],
+ b'refs/heads/super-verboten': [],
+ b'refs/heads/master': ['push']},
b"You do not have permission to push to refs/heads/verboten.\n"
b"You do not have permission to push "
b"to refs/heads/super-verboten.\n")
@@ -271,7 +271,7 @@ class TestUpdateHook(TestCase):
def patched_ancestor_check(self, old, new):
# Avoid a subprocess call to execute on a git repository
# that we haven't created.
- if old == 'old':
+ if old == b'old':
return False
return True
@@ -284,26 +284,26 @@ class TestUpdateHook(TestCase):
def test_fast_forward(self):
# If the old sha is a merge ancestor of the new
self.assertEqual(
- [], hook.match_update_rules({}, ['ref', 'somehex', 'new']))
+ [], hook.match_update_rules({}, [b'ref', b'somehex', b'new']))
def test_rules_fall_through(self):
# The default is to deny
- output = hook.match_update_rules({}, ['ref', 'old', 'new'])
+ output = hook.match_update_rules({}, [b'ref', b'old', b'new'])
self.assertEqual(
[b'You do not have permission to force-push to ref.'], output)
def test_no_matching_ref(self):
# No matches means deny by default
output = hook.match_update_rules(
- {'notamatch': []},
- [b'ref', 'old', 'new'])
+ {b'notamatch': []},
+ [b'ref', b'old', b'new'])
self.assertEqual(
[b'You do not have permission to force-push to ref.'], output)
def test_no_matching_non_utf8_ref(self):
# An unmatched non-UTF-8 ref is denied.
output = hook.match_update_rules(
- {}, [b'refs/heads/\x80', 'old', 'new'])
+ {}, [b'refs/heads/\x80', b'old', b'new'])
self.assertEqual(
[b'You do not have permission to force-push to refs/heads/\x80.'],
output)
@@ -311,7 +311,7 @@ class TestUpdateHook(TestCase):
def test_no_matching_utf8_ref(self):
# An unmatched UTF-8 ref is denied.
output = hook.match_update_rules(
- {}, [u'refs/heads/géag'.encode('UTF-8'), 'old', 'new'])
+ {}, ['refs/heads/géag'.encode('UTF-8'), b'old', b'new'])
self.assertEqual(
[b'You do not have permission to force-push to '
b'refs/heads/g\xc3\xa9ag.'],
@@ -320,8 +320,8 @@ class TestUpdateHook(TestCase):
def test_matching_ref(self):
# Permission given to force-push
output = hook.match_update_rules(
- {'ref': ['force_push']},
- ['ref', 'old', 'new'])
+ {b'ref': ['force_push']},
+ [b'ref', b'old', b'new'])
self.assertEqual([], output)
def test_matching_non_utf8_ref(self):
@@ -341,46 +341,48 @@ class TestUpdateHook(TestCase):
def test_no_permission(self):
# User does not have permission to force-push
output = hook.match_update_rules(
- {'ref': ['create']},
- ['ref', 'old', 'new'])
+ {b'ref': ['create']},
+ [b'ref', b'old', b'new'])
self.assertEqual(
[b'You do not have permission to force-push to ref.'], output)
class TestDeterminePermissions(TestCase):
+ oid_bytes = pygit2.GIT_OID_HEX_ZERO.encode('utf-8')
+
def test_no_match_fallthrough(self):
# No matching rule is deny by default
output = hook.determine_permissions_outcome(
- 'old', 'ref', {})
+ b'old', b'ref', {})
self.assertEqual(b"You do not have permission to push to ref.", output)
def test_match_no_permissions(self):
output = hook.determine_permissions_outcome(
- 'old', 'ref', {'ref': []})
+ b'old', b'ref', {'ref': []})
self.assertEqual(b"You do not have permission to push to ref.", output)
def test_match_with_create(self):
output = hook.determine_permissions_outcome(
- pygit2.GIT_OID_HEX_ZERO, 'ref', {'ref': ['create']})
+ self.oid_bytes, b'ref', {b'ref': ['create']})
self.assertIsNone(output)
def test_match_no_create_perms(self):
output = hook.determine_permissions_outcome(
- pygit2.GIT_OID_HEX_ZERO, 'ref', {'ref': []})
+ self.oid_bytes, b'ref', {b'ref': []})
self.assertEqual(b"You do not have permission to create ref.", output)
def test_push(self):
output = hook.determine_permissions_outcome(
- 'old', 'ref', {'ref': ['push']})
+ b'old', b'ref', {b'ref': ['push']})
self.assertIsNone(output)
def test_force_push(self):
output = hook.determine_permissions_outcome(
- 'old', 'ref', {'ref': ['force_push']})
+ b'old', b'ref', {b'ref': ['force_push']})
self.assertIsNone(output)
def test_force_push_does_not_imply_create(self):
output = hook.determine_permissions_outcome(
- pygit2.GIT_OID_HEX_ZERO, 'ref', {'ref': ['force_push']})
+ pygit2.GIT_OID_HEX_ZERO, b'ref', {b'ref': ['force_push']})
self.assertEqual(b"You do not have permission to create ref.", output)
diff --git a/turnip/pack/tests/test_http.py b/turnip/pack/tests/test_http.py
index 17e662d..1bb88be 100644
--- a/turnip/pack/tests/test_http.py
+++ b/turnip/pack/tests/test_http.py
@@ -33,7 +33,7 @@ class LessDummyRequest(requesthelper.DummyRequest):
@property
def value(self):
- return "".join(self.written)
+ return b"".join(self.written)
def write(self, data):
self.startedWriting = 1
@@ -116,9 +116,9 @@ class ErrorTestMixin(object):
@defer.inlineCallbacks
def test_backend_immediately_dies(self):
# If the backend disappears before it says anything, that's OK.
- yield self.performRequest('')
+ yield self.performRequest(b'')
self.assertEqual(200, self.request.responseCode)
- self.assertEqual('', self.request.value)
+ self.assertEqual(b'', self.request.value)
@defer.inlineCallbacks
def test_backend_virt_error(self):
@@ -126,7 +126,7 @@ class ErrorTestMixin(object):
yield self.performRequest(
helpers.encode_packet(b'ERR turnip virt error: NOT_FOUND enoent'))
self.assertEqual(404, self.request.responseCode)
- self.assertEqual('enoent', self.request.value)
+ self.assertEqual(b'enoent', self.request.value)
@defer.inlineCallbacks
def test_backend_virt_error_unknown(self):
@@ -134,7 +134,7 @@ class ErrorTestMixin(object):
yield self.performRequest(
helpers.encode_packet(b'ERR turnip virt error: random yay'))
self.assertEqual(500, self.request.responseCode)
- self.assertEqual('yay', self.request.value)
+ self.assertEqual(b'yay', self.request.value)
class TestSmartHTTPRefsResource(ErrorTestMixin, TestCase):
@@ -157,13 +157,13 @@ class TestSmartHTTPRefsResource(ErrorTestMixin, TestCase):
yield self.performRequest(service=None)
self.assertEqual(404, self.request.responseCode)
self.assertEqual(
- "Only git smart HTTP clients are supported.", self.request.value)
+ b"Only git smart HTTP clients are supported.", self.request.value)
@defer.inlineCallbacks
def test_unsupported_service(self):
yield self.performRequest(service=b'foo')
# self.assertEqual(403, self.request.responseCode)
- self.assertEqual("Unsupported service.", self.request.value)
+ self.assertEqual(b"Unsupported service.", self.request.value)
@defer.inlineCallbacks
def test_backend_error(self):
@@ -173,7 +173,7 @@ class TestSmartHTTPRefsResource(ErrorTestMixin, TestCase):
yield self.performRequest(
helpers.encode_packet(b'ERR so borked'))
self.assertEqual(500, self.request.responseCode)
- self.assertEqual('so borked', self.request.value)
+ self.assertEqual(b'so borked', self.request.value)
@defer.inlineCallbacks
def test_good(self):
@@ -182,9 +182,9 @@ class TestSmartHTTPRefsResource(ErrorTestMixin, TestCase):
b'And I am raw, since we got a good packet to start with.')
self.assertEqual(200, self.request.responseCode)
self.assertEqual(
- '001e# service=git-upload-pack\n'
- '0000001bI am git protocol data.'
- 'And I am raw, since we got a good packet to start with.',
+ b'001e# service=git-upload-pack\n'
+ b'0000001bI am git protocol data.'
+ b'And I am raw, since we got a good packet to start with.',
self.request.value)
@@ -210,7 +210,7 @@ class TestSmartHTTPCommandResource(ErrorTestMixin, TestCase):
yield self.performRequest(
helpers.encode_packet(b'ERR so borked'))
self.assertEqual(200, self.request.responseCode)
- self.assertEqual('0011ERR so borked', self.request.value)
+ self.assertEqual(b'0011ERR so borked', self.request.value)
@defer.inlineCallbacks
def test_good(self):
@@ -219,8 +219,8 @@ class TestSmartHTTPCommandResource(ErrorTestMixin, TestCase):
b'And I am raw, since we got a good packet to start with.')
self.assertEqual(200, self.request.responseCode)
self.assertEqual(
- '001bI am git protocol data.'
- 'And I am raw, since we got a good packet to start with.',
+ b'001bI am git protocol data.'
+ b'And I am raw, since we got a good packet to start with.',
self.request.value)
@@ -248,4 +248,4 @@ class TestHTTPAuthRootResource(TestCase):
root.reactor.advance(15)
self.assertTrue(d.called)
self.assertEqual(504, request.responseCode)
- self.assertEqual('Path translation timed out.', request.value)
+ self.assertEqual(b'Path translation timed out.', request.value)
diff --git a/turnip/tests/test_helpers.py b/turnip/tests/test_helpers.py
index 9dc4101..fdeb455 100644
--- a/turnip/tests/test_helpers.py
+++ b/turnip/tests/test_helpers.py
@@ -18,24 +18,24 @@ class TestComposePath(TestCase):
def test_basic(self):
# The path is resolved within the given root tree.
self.assertEqual(
- b'/foo/bar/baz/quux',
- helpers.compose_path(b'/foo/bar', b'baz/quux'))
+ '/foo/bar/baz/quux',
+ helpers.compose_path('/foo/bar', 'baz/quux'))
def test_absolute(self):
# Even absolute paths are contained.
self.assertEqual(
- b'/foo/bar/baz/quux',
- helpers.compose_path(b'/foo/bar', b'/baz/quux'))
+ '/foo/bar/baz/quux',
+ helpers.compose_path('/foo/bar', '/baz/quux'))
def test_normalises(self):
# Paths are normalised.
self.assertEqual(
- b'/foo/bar/baz/quux',
- helpers.compose_path(b'///foo/.//bar', b'//baz/..//baz/./quux'))
+ '/foo/bar/baz/quux',
+ helpers.compose_path('///foo/.//bar', '//baz/..//baz/./quux'))
def test_no_escape(self):
# You can't get out.
self.assertRaises(
- ValueError, helpers.compose_path, b'/foo', b'../bar')
+ ValueError, helpers.compose_path, '/foo', '../bar')
self.assertRaises(
- ValueError, helpers.compose_path, b'/foo', b'/foo/../../bar')
+ ValueError, helpers.compose_path, '/foo', '/foo/../../bar')