Merge ~cjwatson/turnip:set-symbolic-ref into turnip:master
- Git
- lp:~cjwatson/turnip
- set-symbolic-ref
- Merge into master
Proposed by
Colin Watson
Status: | Merged |
---|---|
Approved by: | Colin Watson |
Approved revision: | eea2842e739a4c3d3314f31c7d8937f7cc2542e2 |
Merged at revision: | cf713d479b3fd067cb2a837428b276565b33f0a4 |
Proposed branch: | ~cjwatson/turnip:set-symbolic-ref |
Merge into: | turnip:master |
Diff against target: |
497 lines (+297/-26) 8 files modified
README (+16/-0) turnip/pack/git.py (+83/-9) turnip/pack/hookrpc.py (+6/-2) turnip/pack/http.py (+5/-2) turnip/pack/ssh.py (+2/-1) turnip/pack/tests/test_functional.py (+65/-11) turnip/pack/tests/test_git.py (+118/-0) turnip/pack/tests/test_http.py (+2/-1) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Otto Co-Pilot | Needs Fixing | ||
William Grant | code | Approve | |
Review via email: mp+309702@code.launchpad.net |
Commit message
Add turnip-
This makes it possible to set a repository's HEAD by talking directly to
turnip over HTTPS, which is needed for git-to-git code imports.
LP: #1469459
Description of the change
To post a comment you must log in.
Revision history for this message
William Grant (wgrant) : | # |
review:
Approve
(code)
Revision history for this message
Colin Watson (cjwatson) wrote : | # |
Revision history for this message
Otto Co-Pilot (otto-copilot) wrote : | # |
I tried to merge it but there are some problems. Typically you want to merge or rebase and try again.
review:
Needs Fixing
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | diff --git a/README b/README |
2 | index 7b26510..4ebc443 100644 |
3 | --- a/README |
4 | +++ b/README |
5 | @@ -68,6 +68,22 @@ The only additional parameters implemented today are |
6 | 'turnip-stateless-rpc' and 'turnip-advertise-refs', which are used by |
7 | the smart HTTP server to proxy to the standard pack protocol. |
8 | |
9 | +turnip implements one externally-visible extension: a |
10 | +'turnip-set-symbolic-ref' service that sets a symbolic ref (currently only |
11 | +'HEAD' is permitted) to a given target. This may be used over the various |
12 | +protocols (git, SSH, smart HTTP), requesting the service in the same way as |
13 | +the existing 'git-upload-pack' and 'git-receive-pack' services. |
14 | + |
15 | + turnip-set-symbolic-ref-request = set-symbolic-ref-line |
16 | + flush-pkt |
17 | + set-symbolic-ref-line = PKT-LINE(refname SP refname) |
18 | + |
19 | +The server replies with an ACK indicating the symbolic ref name that was |
20 | +changed, or an error message. |
21 | + |
22 | + turnip-set-symbolic-ref-response = set-symbolic-ref-ack / error-line |
23 | + set-symbolic-ref-ack = PKT-LINE("ACK" SP refname) |
24 | + |
25 | |
26 | Development |
27 | =========== |
28 | diff --git a/turnip/pack/git.py b/turnip/pack/git.py |
29 | index 0e5d928..93bc91b 100644 |
30 | --- a/turnip/pack/git.py |
31 | +++ b/turnip/pack/git.py |
32 | @@ -278,7 +278,7 @@ class GitProcessProtocol(protocol.ProcessProtocol): |
33 | 'git exited {code} with no output; synthesising an error', |
34 | code=code) |
35 | self.peer.sendPacket(ERROR_PREFIX + 'backend exited %d' % code) |
36 | - self.peer.transport.loseConnection() |
37 | + self.peer.processEnded(reason) |
38 | |
39 | def pauseProducing(self): |
40 | self.transport.pauseProducing() |
41 | @@ -392,43 +392,117 @@ class PackBackendProtocol(PackServerProtocol): |
42 | """ |
43 | |
44 | hookrpc_key = None |
45 | + expect_set_symbolic_ref = False |
46 | |
47 | def requestReceived(self, command, raw_pathname, params): |
48 | self.extractRequestMeta(command, raw_pathname, params) |
49 | + self.command = command |
50 | + self.raw_pathname = raw_pathname |
51 | + self.path = compose_path(self.factory.root, self.raw_pathname) |
52 | + |
53 | + if command == b'turnip-set-symbolic-ref': |
54 | + self.expect_set_symbolic_ref = True |
55 | + self.resumeProducing() |
56 | + return |
57 | |
58 | - path = compose_path(self.factory.root, raw_pathname) |
59 | + write_operation = False |
60 | if command == b'git-upload-pack': |
61 | subcmd = b'upload-pack' |
62 | elif command == b'git-receive-pack': |
63 | subcmd = b'receive-pack' |
64 | + write_operation = True |
65 | else: |
66 | self.die(b'Unsupported command in request') |
67 | return |
68 | |
69 | - cmd = b'git' |
70 | - args = [b'git', subcmd] |
71 | + args = [] |
72 | if params.pop(b'turnip-stateless-rpc', None): |
73 | args.append(b'--stateless-rpc') |
74 | if params.pop(b'turnip-advertise-refs', None): |
75 | args.append(b'--advertise-refs') |
76 | - args.append(path) |
77 | + args.append(self.path) |
78 | + self.spawnGit(subcmd, args, write_operation=write_operation) |
79 | + |
80 | + def spawnGit(self, subcmd, extra_args, write_operation=False, |
81 | + send_path_as_option=False): |
82 | + cmd = b'git' |
83 | + args = [b'git'] |
84 | + if send_path_as_option: |
85 | + args.extend([b'-C', self.path]) |
86 | + args.append(subcmd) |
87 | + args.extend(extra_args) |
88 | |
89 | env = {} |
90 | - if subcmd == b'receive-pack' and self.factory.hookrpc_handler: |
91 | + if write_operation and self.factory.hookrpc_handler: |
92 | # This is a write operation, so prepare config, hooks, the hook |
93 | # RPC server, and the environment variables that link them up. |
94 | - ensure_config(path) |
95 | + ensure_config(self.path) |
96 | self.hookrpc_key = str(uuid.uuid4()) |
97 | self.factory.hookrpc_handler.registerKey( |
98 | - self.hookrpc_key, raw_pathname, []) |
99 | - ensure_hooks(path) |
100 | + self.hookrpc_key, self.raw_pathname, []) |
101 | + ensure_hooks(self.path) |
102 | env[b'TURNIP_HOOK_RPC_SOCK'] = self.factory.hookrpc_sock |
103 | env[b'TURNIP_HOOK_RPC_KEY'] = self.hookrpc_key |
104 | |
105 | self.log.info('Spawning {args}', args=args) |
106 | self.peer = GitProcessProtocol(self) |
107 | + self.spawnProcess(cmd, args, env=env) |
108 | + |
109 | + def spawnProcess(self, cmd, args, env=None): |
110 | reactor.spawnProcess(self.peer, cmd, args, env=env) |
111 | |
112 | + def packetReceived(self, data): |
113 | + if self.expect_set_symbolic_ref: |
114 | + if data is None: |
115 | + self.die(b'Bad request: flush-pkt instead') |
116 | + return |
117 | + self.pauseProducing() |
118 | + self.expect_set_symbolic_ref = False |
119 | + if b' ' not in data: |
120 | + self.die(b'Invalid set-symbolic-ref-line') |
121 | + return |
122 | + name, target = data.split(b' ', 1) |
123 | + # Be careful about extending this to anything other than HEAD. |
124 | + # We use "git symbolic-ref" because it gives us locking and |
125 | + # logging, but it doesn't prevent writing a ref to ../something. |
126 | + # Fortunately it does at least refuse to point HEAD outside of |
127 | + # refs/. |
128 | + if name != b'HEAD': |
129 | + self.die(b'Symbolic ref name must be "HEAD"') |
130 | + return |
131 | + if target.startswith(b'-'): |
132 | + self.die(b'Symbolic ref target may not start with "-"') |
133 | + return |
134 | + elif b' ' in target: |
135 | + self.die(b'Symbolic ref target may not contain " "') |
136 | + return |
137 | + self.symbolic_ref_name = name |
138 | + self.spawnGit( |
139 | + b'symbolic-ref', [name, target], write_operation=True, |
140 | + send_path_as_option=True) |
141 | + return |
142 | + |
143 | + PackServerProtocol.packetReceived(self, data) |
144 | + |
145 | + @defer.inlineCallbacks |
146 | + def processEnded(self, reason): |
147 | + message = None |
148 | + if self.command == b'turnip-set-symbolic-ref': |
149 | + if reason.check(error.ProcessDone): |
150 | + try: |
151 | + yield self.factory.hookrpc_handler.notify(self.path) |
152 | + self.sendPacket(b'ACK %s\n' % self.symbolic_ref_name) |
153 | + except Exception as e: |
154 | + message = str(e) |
155 | + else: |
156 | + message = ( |
157 | + 'git symbolic-ref exited with status %d' % |
158 | + reason.value.exitCode) |
159 | + if message is None: |
160 | + self.transport.loseConnection() |
161 | + else: |
162 | + self.die(message) |
163 | + |
164 | def readConnectionLost(self): |
165 | # Forward the closed stdin down the stack. |
166 | if self.peer is not None: |
167 | diff --git a/turnip/pack/hookrpc.py b/turnip/pack/hookrpc.py |
168 | index d273a9c..bd3bc0e 100644 |
169 | --- a/turnip/pack/hookrpc.py |
170 | +++ b/turnip/pack/hookrpc.py |
171 | @@ -126,11 +126,15 @@ class HookRPCHandler(object): |
172 | return [rule.decode('utf-8') for rule in self.ref_rules[args['key']]] |
173 | |
174 | @defer.inlineCallbacks |
175 | + def notify(self, path): |
176 | + proxy = xmlrpc.Proxy(self.virtinfo_url, allowNone=True) |
177 | + yield proxy.callRemote(b'notify', path) |
178 | + |
179 | + @defer.inlineCallbacks |
180 | def notifyPush(self, proto, args): |
181 | """Notify the virtinfo service about a push.""" |
182 | path = self.ref_paths[args['key']] |
183 | - proxy = xmlrpc.Proxy(self.virtinfo_url, allowNone=True) |
184 | - yield proxy.callRemote(b'notify', path) |
185 | + yield self.notify(path) |
186 | |
187 | |
188 | class HookRPCServerFactory(RPCServerFactory): |
189 | diff --git a/turnip/pack/http.py b/turnip/pack/http.py |
190 | index 5e5f87e..42fed27 100644 |
191 | --- a/turnip/pack/http.py |
192 | +++ b/turnip/pack/http.py |
193 | @@ -680,7 +680,8 @@ class HTTPAuthResource(resource.Resource): |
194 | class SmartHTTPFrontendResource(resource.Resource): |
195 | """HTTP resource to translate Git smart HTTP requests to pack protocol.""" |
196 | |
197 | - allowed_services = frozenset((b'git-upload-pack', b'git-receive-pack')) |
198 | + allowed_services = frozenset(( |
199 | + b'git-upload-pack', b'git-receive-pack', b'turnip-set-symbolic-ref')) |
200 | |
201 | def __init__(self, backend_host, config): |
202 | resource.Resource.__init__(self) |
203 | @@ -737,7 +738,9 @@ class SmartHTTPFrontendResource(resource.Resource): |
204 | content_type = request.getHeader(b'Content-Type') |
205 | if content_type is None: |
206 | return False |
207 | - return content_type.startswith(b'application/x-git-') |
208 | + return ( |
209 | + content_type.startswith(b'application/x-git-') or |
210 | + content_type.startswith(b'application/x-turnip-')) |
211 | |
212 | def getChild(self, path, request): |
213 | if self._isGitRequest(request): |
214 | diff --git a/turnip/pack/ssh.py b/turnip/pack/ssh.py |
215 | index 006331d..b753220 100644 |
216 | --- a/turnip/pack/ssh.py |
217 | +++ b/turnip/pack/ssh.py |
218 | @@ -120,7 +120,8 @@ class SSHPackClientFactory(protocol.ClientFactory): |
219 | class SmartSSHSession(DoNothingSession): |
220 | """SSH session allowing only Git smart SSH requests.""" |
221 | |
222 | - allowed_services = frozenset((b'git-upload-pack', b'git-receive-pack')) |
223 | + allowed_services = frozenset(( |
224 | + b'git-upload-pack', b'git-receive-pack', b'turnip-set-symbolic-ref')) |
225 | |
226 | def __init__(self, *args, **kwargs): |
227 | super(SmartSSHSession, self).__init__(*args, **kwargs) |
228 | diff --git a/turnip/pack/tests/test_functional.py b/turnip/pack/tests/test_functional.py |
229 | index 61fd5c4..e6dbd20 100644 |
230 | --- a/turnip/pack/tests/test_functional.py |
231 | +++ b/turnip/pack/tests/test_functional.py |
232 | @@ -8,8 +8,10 @@ from __future__ import ( |
233 | unicode_literals, |
234 | ) |
235 | |
236 | +import base64 |
237 | from collections import defaultdict |
238 | import hashlib |
239 | +import io |
240 | import os |
241 | import random |
242 | import shutil |
243 | @@ -33,10 +35,7 @@ from fixtures import ( |
244 | from lazr.sshserver.auth import NoSuchPersonWithName |
245 | from testtools import TestCase |
246 | from testtools.content import text_content |
247 | -from testtools.deferredruntest import ( |
248 | - assert_fails_with, |
249 | - AsynchronousDeferredRunTest, |
250 | - ) |
251 | +from testtools.deferredruntest import AsynchronousDeferredRunTest |
252 | from testtools.matchers import StartsWith |
253 | from twisted.internet import ( |
254 | defer, |
255 | @@ -45,11 +44,12 @@ from twisted.internet import ( |
256 | ) |
257 | from twisted.web import ( |
258 | client, |
259 | - error, |
260 | + http_headers, |
261 | server, |
262 | xmlrpc, |
263 | ) |
264 | |
265 | +from turnip.pack import helpers |
266 | from turnip.pack.git import ( |
267 | PackBackendFactory, |
268 | PackFrontendFactory, |
269 | @@ -480,14 +480,68 @@ class TestSmartHTTPFrontendFunctional(FrontendFunctionalTestMixin, TestCase): |
270 | |
271 | @defer.inlineCallbacks |
272 | def test_root_revision_header(self): |
273 | - factory = client.HTTPClientFactory( |
274 | - b'http://localhost:%d/' % self.port, method=b'HEAD', |
275 | - followRedirect=False) |
276 | - reactor.connectTCP(b'localhost', self.port, factory) |
277 | - yield assert_fails_with(factory.deferred, error.PageRedirect) |
278 | + response = yield client.Agent(reactor).request( |
279 | + b'HEAD', b'http://localhost:%d/' % self.port) |
280 | + self.assertEqual(302, response.code) |
281 | self.assertEqual( |
282 | [version_info['revision_id']], |
283 | - factory.response_headers[b'x-turnip-revision']) |
284 | + response.headers.getRawHeaders(b'X-Turnip-Revision')) |
285 | + |
286 | + def make_set_symbolic_ref_request(self, line): |
287 | + parsed_url = urlsplit(self.url) |
288 | + url = urlunsplit([ |
289 | + parsed_url.scheme, |
290 | + b'%s:%d' % (parsed_url.hostname, parsed_url.port), |
291 | + parsed_url.path + b'/turnip-set-symbolic-ref', b'', b'']) |
292 | + headers = { |
293 | + b'Content-Type': [ |
294 | + b'application/x-turnip-set-symbolic-ref-request', |
295 | + ], |
296 | + } |
297 | + if parsed_url.username: |
298 | + headers[b'Authorization'] = [ |
299 | + b'Basic ' + base64.b64encode( |
300 | + b'%s:%s' % (parsed_url.username, parsed_url.password)), |
301 | + ] |
302 | + data = helpers.encode_packet(line) + helpers.encode_packet(None) |
303 | + return client.Agent(reactor).request( |
304 | + b'POST', url, headers=http_headers.Headers(headers), |
305 | + bodyProducer=client.FileBodyProducer(io.BytesIO(data))) |
306 | + |
307 | + @defer.inlineCallbacks |
308 | + def get_symbolic_ref(self, path, name): |
309 | + out = yield utils.getProcessOutput( |
310 | + b'git', (b'symbolic-ref', name), env=os.environ, path=path) |
311 | + defer.returnValue(out.rstrip(b'\n')) |
312 | + |
313 | + @defer.inlineCallbacks |
314 | + def test_turnip_set_symbolic_ref(self): |
315 | + repo = os.path.join(self.root, self.internal_name) |
316 | + head_target = yield self.get_symbolic_ref(repo, b'HEAD') |
317 | + self.assertEqual(b'refs/heads/master', head_target) |
318 | + response = yield self.make_set_symbolic_ref_request( |
319 | + b'HEAD refs/heads/new-head') |
320 | + self.assertEqual(200, response.code) |
321 | + body = yield client.readBody(response) |
322 | + self.assertEqual((b'ACK HEAD\n', ''), helpers.decode_packet(body)) |
323 | + head_target = yield self.get_symbolic_ref(repo, b'HEAD') |
324 | + self.assertEqual(b'refs/heads/new-head', head_target) |
325 | + |
326 | + @defer.inlineCallbacks |
327 | + def test_turnip_set_symbolic_ref_error(self): |
328 | + repo = os.path.join(self.root, self.internal_name) |
329 | + head_target = yield self.get_symbolic_ref(repo, b'HEAD') |
330 | + self.assertEqual(b'refs/heads/master', head_target) |
331 | + response = yield self.make_set_symbolic_ref_request(b'HEAD --evil') |
332 | + # This is a little weird since an error occurred, but it's |
333 | + # consistent with other HTTP pack protocol responses. |
334 | + self.assertEqual(200, response.code) |
335 | + body = yield client.readBody(response) |
336 | + self.assertEqual( |
337 | + (b'ERR Symbolic ref target may not start with "-"\n', ''), |
338 | + helpers.decode_packet(body)) |
339 | + head_target = yield self.get_symbolic_ref(repo, b'HEAD') |
340 | + self.assertEqual(b'refs/heads/master', head_target) |
341 | |
342 | |
343 | class TestSmartHTTPFrontendWithAuthFunctional(TestSmartHTTPFrontendFunctional): |
344 | diff --git a/turnip/pack/tests/test_git.py b/turnip/pack/tests/test_git.py |
345 | index d19574d..4153234 100644 |
346 | --- a/turnip/pack/tests/test_git.py |
347 | +++ b/turnip/pack/tests/test_git.py |
348 | @@ -7,13 +7,23 @@ from __future__ import ( |
349 | unicode_literals, |
350 | ) |
351 | |
352 | +import os.path |
353 | + |
354 | +from fixtures import TempDir |
355 | +from pygit2 import init_repository |
356 | from testtools import TestCase |
357 | +from testtools.matchers import ( |
358 | + ContainsDict, |
359 | + Equals, |
360 | + MatchesListwise, |
361 | + ) |
362 | from twisted.test import proto_helpers |
363 | |
364 | from turnip.pack import ( |
365 | git, |
366 | helpers, |
367 | ) |
368 | +from turnip.pack.tests.test_hooks import MockHookRPCHandler |
369 | |
370 | |
371 | class DummyPackServerProtocol(git.PackServerProtocol): |
372 | @@ -87,3 +97,111 @@ class TestPackServerProtocol(TestCase): |
373 | # dropped. |
374 | self.proto.dataReceived(b'0000') |
375 | self.assertKilledWith(b'Bad request: flush-pkt instead') |
376 | + |
377 | + |
378 | +class DummyPackBackendProtocol(git.PackBackendProtocol): |
379 | + |
380 | + test_process = None |
381 | + |
382 | + def spawnProcess(self, cmd, args, env=None): |
383 | + if self.test_process is not None: |
384 | + raise AssertionError('Process already spawned.') |
385 | + self.test_process = (cmd, args, env) |
386 | + |
387 | + |
388 | +class TestPackBackendProtocol(TestCase): |
389 | + """Test the Git pack backend protocol.""" |
390 | + |
391 | + def setUp(self): |
392 | + super(TestPackBackendProtocol, self).setUp() |
393 | + self.root = self.useFixture(TempDir()).path |
394 | + self.hookrpc_handler = MockHookRPCHandler() |
395 | + self.hookrpc_sock = os.path.join(self.root, 'hookrpc_sock') |
396 | + self.factory = git.PackBackendFactory( |
397 | + self.root, self.hookrpc_handler, self.hookrpc_sock) |
398 | + self.proto = DummyPackBackendProtocol() |
399 | + self.proto.factory = self.factory |
400 | + self.transport = proto_helpers.StringTransportWithDisconnection() |
401 | + self.transport.protocol = self.proto |
402 | + self.proto.makeConnection(self.transport) |
403 | + |
404 | + def assertKilledWith(self, message): |
405 | + self.assertFalse(self.transport.connected) |
406 | + self.assertEqual( |
407 | + (b'ERR ' + message + b'\n', b''), |
408 | + helpers.decode_packet(self.transport.value())) |
409 | + |
410 | + def test_git_upload_pack_calls_spawnProcess(self): |
411 | + # If the command is git-upload-pack, requestReceived calls |
412 | + # spawnProcess with appropriate arguments. |
413 | + self.proto.requestReceived( |
414 | + b'git-upload-pack', b'/foo.git', {b'host': b'example.com'}) |
415 | + self.assertEqual( |
416 | + (b'git', |
417 | + [b'git', b'upload-pack', os.path.join(self.root, b'foo.git')], |
418 | + {}), |
419 | + self.proto.test_process) |
420 | + |
421 | + def test_git_receive_pack_calls_spawnProcess(self): |
422 | + # If the command is git-receive-pack, requestReceived calls |
423 | + # spawnProcess with appropriate arguments. |
424 | + repo_dir = os.path.join(self.root, 'foo.git') |
425 | + init_repository(repo_dir, bare=True) |
426 | + self.proto.requestReceived( |
427 | + b'git-receive-pack', b'/foo.git', {b'host': b'example.com'}) |
428 | + self.assertThat( |
429 | + self.proto.test_process, MatchesListwise([ |
430 | + Equals(b'git'), |
431 | + Equals([b'git', b'receive-pack', repo_dir.encode('utf-8')]), |
432 | + ContainsDict( |
433 | + {b'TURNIP_HOOK_RPC_SOCK': Equals(self.hookrpc_sock)})])) |
434 | + |
435 | + def test_turnip_set_symbolic_ref_calls_spawnProcess(self): |
436 | + # If the command is turnip-set-symbolic-ref, requestReceived does |
437 | + # not spawn a process, but packetReceived calls spawnProcess with |
438 | + # appropriate arguments. |
439 | + repo_dir = os.path.join(self.root, 'foo.git') |
440 | + init_repository(repo_dir, bare=True) |
441 | + self.proto.requestReceived(b'turnip-set-symbolic-ref', b'/foo.git', {}) |
442 | + self.assertIsNone(self.proto.test_process) |
443 | + self.proto.packetReceived(b'HEAD refs/heads/master') |
444 | + self.assertThat( |
445 | + self.proto.test_process, MatchesListwise([ |
446 | + Equals(b'git'), |
447 | + Equals([ |
448 | + b'git', b'-C', repo_dir.encode('utf-8'), b'symbolic-ref', |
449 | + b'HEAD', b'refs/heads/master']), |
450 | + ContainsDict( |
451 | + {b'TURNIP_HOOK_RPC_SOCK': Equals(self.hookrpc_sock)})])) |
452 | + |
453 | + def test_turnip_set_symbolic_ref_requires_valid_line(self): |
454 | + # The turnip-set-symbolic-ref command requires a valid |
455 | + # set-symbolic-ref-line packet. |
456 | + self.proto.requestReceived(b'turnip-set-symbolic-ref', b'/foo.git', {}) |
457 | + self.assertIsNone(self.proto.test_process) |
458 | + self.proto.packetReceived(b'HEAD') |
459 | + self.assertKilledWith(b'Invalid set-symbolic-ref-line') |
460 | + |
461 | + def test_turnip_set_symbolic_ref_name_must_be_HEAD(self): |
462 | + # The turnip-set-symbolic-ref command's "name" parameter must be |
463 | + # "HEAD". |
464 | + self.proto.requestReceived(b'turnip-set-symbolic-ref', b'/foo.git', {}) |
465 | + self.assertIsNone(self.proto.test_process) |
466 | + self.proto.packetReceived(b'another-symref refs/heads/master') |
467 | + self.assertKilledWith(b'Symbolic ref name must be "HEAD"') |
468 | + |
469 | + def test_turnip_set_symbolic_ref_target_not_option(self): |
470 | + # The turnip-set-symbolic-ref command's "target" parameter may not |
471 | + # start with "-". |
472 | + self.proto.requestReceived(b'turnip-set-symbolic-ref', b'/foo.git', {}) |
473 | + self.assertIsNone(self.proto.test_process) |
474 | + self.proto.packetReceived(b'HEAD --evil') |
475 | + self.assertKilledWith(b'Symbolic ref target may not start with "-"') |
476 | + |
477 | + def test_turnip_set_symbolic_ref_target_no_space(self): |
478 | + # The turnip-set-symbolic-ref command's "target" parameter may not |
479 | + # contain " ". |
480 | + self.proto.requestReceived(b'turnip-set-symbolic-ref', b'/foo.git', {}) |
481 | + self.assertIsNone(self.proto.test_process) |
482 | + self.proto.packetReceived(b'HEAD evil lies') |
483 | + self.assertKilledWith(b'Symbolic ref target may not contain " "') |
484 | diff --git a/turnip/pack/tests/test_http.py b/turnip/pack/tests/test_http.py |
485 | index 45cafad..6e16b77 100644 |
486 | --- a/turnip/pack/tests/test_http.py |
487 | +++ b/turnip/pack/tests/test_http.py |
488 | @@ -64,7 +64,8 @@ def render_resource(resource, request): |
489 | |
490 | class FakeRoot(object): |
491 | |
492 | - allowed_services = frozenset((b'git-upload-pack', b'git-receive-pack')) |
493 | + allowed_services = frozenset(( |
494 | + b'git-upload-pack', b'git-receive-pack', b'turnip-set-symbolic-ref')) |
495 | |
496 | def __init__(self): |
497 | self.backend_transport = None |
All fixed, thanks.