##// END OF EJS Templates
wireproto: fix pushkey hook failure and output on remote http repo...
Wagner Bruna -
r17793:8474be44 default
parent child Browse files
Show More
@@ -1,640 +1,654 b''
1 1 # wireproto.py - generic wire protocol support functions
2 2 #
3 3 # Copyright 2005-2010 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 import urllib, tempfile, os, sys
9 9 from i18n import _
10 10 from node import bin, hex
11 11 import changegroup as changegroupmod
12 12 import peer, error, encoding, util, store
13 13 import discovery, phases
14 14
15 15 # abstract batching support
16 16
17 17 class future(object):
18 18 '''placeholder for a value to be set later'''
19 19 def set(self, value):
20 20 if util.safehasattr(self, 'value'):
21 21 raise error.RepoError("future is already set")
22 22 self.value = value
23 23
24 24 class batcher(object):
25 25 '''base class for batches of commands submittable in a single request
26 26
27 27 All methods invoked on instances of this class are simply queued and
28 28 return a a future for the result. Once you call submit(), all the queued
29 29 calls are performed and the results set in their respective futures.
30 30 '''
31 31 def __init__(self):
32 32 self.calls = []
33 33 def __getattr__(self, name):
34 34 def call(*args, **opts):
35 35 resref = future()
36 36 self.calls.append((name, args, opts, resref,))
37 37 return resref
38 38 return call
39 39 def submit(self):
40 40 pass
41 41
42 42 class localbatch(batcher):
43 43 '''performs the queued calls directly'''
44 44 def __init__(self, local):
45 45 batcher.__init__(self)
46 46 self.local = local
47 47 def submit(self):
48 48 for name, args, opts, resref in self.calls:
49 49 resref.set(getattr(self.local, name)(*args, **opts))
50 50
51 51 class remotebatch(batcher):
52 52 '''batches the queued calls; uses as few roundtrips as possible'''
53 53 def __init__(self, remote):
54 54 '''remote must support _submitbatch(encbatch) and
55 55 _submitone(op, encargs)'''
56 56 batcher.__init__(self)
57 57 self.remote = remote
58 58 def submit(self):
59 59 req, rsp = [], []
60 60 for name, args, opts, resref in self.calls:
61 61 mtd = getattr(self.remote, name)
62 62 batchablefn = getattr(mtd, 'batchable', None)
63 63 if batchablefn is not None:
64 64 batchable = batchablefn(mtd.im_self, *args, **opts)
65 65 encargsorres, encresref = batchable.next()
66 66 if encresref:
67 67 req.append((name, encargsorres,))
68 68 rsp.append((batchable, encresref, resref,))
69 69 else:
70 70 resref.set(encargsorres)
71 71 else:
72 72 if req:
73 73 self._submitreq(req, rsp)
74 74 req, rsp = [], []
75 75 resref.set(mtd(*args, **opts))
76 76 if req:
77 77 self._submitreq(req, rsp)
78 78 def _submitreq(self, req, rsp):
79 79 encresults = self.remote._submitbatch(req)
80 80 for encres, r in zip(encresults, rsp):
81 81 batchable, encresref, resref = r
82 82 encresref.set(encres)
83 83 resref.set(batchable.next())
84 84
85 85 def batchable(f):
86 86 '''annotation for batchable methods
87 87
88 88 Such methods must implement a coroutine as follows:
89 89
90 90 @batchable
91 91 def sample(self, one, two=None):
92 92 # Handle locally computable results first:
93 93 if not one:
94 94 yield "a local result", None
95 95 # Build list of encoded arguments suitable for your wire protocol:
96 96 encargs = [('one', encode(one),), ('two', encode(two),)]
97 97 # Create future for injection of encoded result:
98 98 encresref = future()
99 99 # Return encoded arguments and future:
100 100 yield encargs, encresref
101 101 # Assuming the future to be filled with the result from the batched
102 102 # request now. Decode it:
103 103 yield decode(encresref.value)
104 104
105 105 The decorator returns a function which wraps this coroutine as a plain
106 106 method, but adds the original method as an attribute called "batchable",
107 107 which is used by remotebatch to split the call into separate encoding and
108 108 decoding phases.
109 109 '''
110 110 def plain(*args, **opts):
111 111 batchable = f(*args, **opts)
112 112 encargsorres, encresref = batchable.next()
113 113 if not encresref:
114 114 return encargsorres # a local result in this case
115 115 self = args[0]
116 116 encresref.set(self._submitone(f.func_name, encargsorres))
117 117 return batchable.next()
118 118 setattr(plain, 'batchable', f)
119 119 return plain
120 120
121 121 # list of nodes encoding / decoding
122 122
123 123 def decodelist(l, sep=' '):
124 124 if l:
125 125 return map(bin, l.split(sep))
126 126 return []
127 127
128 128 def encodelist(l, sep=' '):
129 129 return sep.join(map(hex, l))
130 130
131 131 # batched call argument encoding
132 132
133 133 def escapearg(plain):
134 134 return (plain
135 135 .replace(':', '::')
136 136 .replace(',', ':,')
137 137 .replace(';', ':;')
138 138 .replace('=', ':='))
139 139
140 140 def unescapearg(escaped):
141 141 return (escaped
142 142 .replace(':=', '=')
143 143 .replace(':;', ';')
144 144 .replace(':,', ',')
145 145 .replace('::', ':'))
146 146
147 147 # client side
148 148
149 149 def todict(**args):
150 150 return args
151 151
152 152 class wirepeer(peer.peerrepository):
153 153
154 154 def batch(self):
155 155 return remotebatch(self)
156 156 def _submitbatch(self, req):
157 157 cmds = []
158 158 for op, argsdict in req:
159 159 args = ','.join('%s=%s' % p for p in argsdict.iteritems())
160 160 cmds.append('%s %s' % (op, args))
161 161 rsp = self._call("batch", cmds=';'.join(cmds))
162 162 return rsp.split(';')
163 163 def _submitone(self, op, args):
164 164 return self._call(op, **args)
165 165
166 166 @batchable
167 167 def lookup(self, key):
168 168 self.requirecap('lookup', _('look up remote revision'))
169 169 f = future()
170 170 yield todict(key=encoding.fromlocal(key)), f
171 171 d = f.value
172 172 success, data = d[:-1].split(" ", 1)
173 173 if int(success):
174 174 yield bin(data)
175 175 self._abort(error.RepoError(data))
176 176
177 177 @batchable
178 178 def heads(self):
179 179 f = future()
180 180 yield {}, f
181 181 d = f.value
182 182 try:
183 183 yield decodelist(d[:-1])
184 184 except ValueError:
185 185 self._abort(error.ResponseError(_("unexpected response:"), d))
186 186
187 187 @batchable
188 188 def known(self, nodes):
189 189 f = future()
190 190 yield todict(nodes=encodelist(nodes)), f
191 191 d = f.value
192 192 try:
193 193 yield [bool(int(f)) for f in d]
194 194 except ValueError:
195 195 self._abort(error.ResponseError(_("unexpected response:"), d))
196 196
197 197 @batchable
198 198 def branchmap(self):
199 199 f = future()
200 200 yield {}, f
201 201 d = f.value
202 202 try:
203 203 branchmap = {}
204 204 for branchpart in d.splitlines():
205 205 branchname, branchheads = branchpart.split(' ', 1)
206 206 branchname = encoding.tolocal(urllib.unquote(branchname))
207 207 branchheads = decodelist(branchheads)
208 208 branchmap[branchname] = branchheads
209 209 yield branchmap
210 210 except TypeError:
211 211 self._abort(error.ResponseError(_("unexpected response:"), d))
212 212
213 213 def branches(self, nodes):
214 214 n = encodelist(nodes)
215 215 d = self._call("branches", nodes=n)
216 216 try:
217 217 br = [tuple(decodelist(b)) for b in d.splitlines()]
218 218 return br
219 219 except ValueError:
220 220 self._abort(error.ResponseError(_("unexpected response:"), d))
221 221
222 222 def between(self, pairs):
223 223 batch = 8 # avoid giant requests
224 224 r = []
225 225 for i in xrange(0, len(pairs), batch):
226 226 n = " ".join([encodelist(p, '-') for p in pairs[i:i + batch]])
227 227 d = self._call("between", pairs=n)
228 228 try:
229 229 r.extend(l and decodelist(l) or [] for l in d.splitlines())
230 230 except ValueError:
231 231 self._abort(error.ResponseError(_("unexpected response:"), d))
232 232 return r
233 233
234 234 @batchable
235 235 def pushkey(self, namespace, key, old, new):
236 236 if not self.capable('pushkey'):
237 237 yield False, None
238 238 f = future()
239 239 self.ui.debug('preparing pushkey for "%s:%s"\n' % (namespace, key))
240 240 yield todict(namespace=encoding.fromlocal(namespace),
241 241 key=encoding.fromlocal(key),
242 242 old=encoding.fromlocal(old),
243 243 new=encoding.fromlocal(new)), f
244 244 d = f.value
245 245 d, output = d.split('\n', 1)
246 246 try:
247 247 d = bool(int(d))
248 248 except ValueError:
249 249 raise error.ResponseError(
250 250 _('push failed (unexpected response):'), d)
251 251 for l in output.splitlines(True):
252 252 self.ui.status(_('remote: '), l)
253 253 yield d
254 254
255 255 @batchable
256 256 def listkeys(self, namespace):
257 257 if not self.capable('pushkey'):
258 258 yield {}, None
259 259 f = future()
260 260 self.ui.debug('preparing listkeys for "%s"\n' % namespace)
261 261 yield todict(namespace=encoding.fromlocal(namespace)), f
262 262 d = f.value
263 263 r = {}
264 264 for l in d.splitlines():
265 265 k, v = l.split('\t')
266 266 r[encoding.tolocal(k)] = encoding.tolocal(v)
267 267 yield r
268 268
269 269 def stream_out(self):
270 270 return self._callstream('stream_out')
271 271
272 272 def changegroup(self, nodes, kind):
273 273 n = encodelist(nodes)
274 274 f = self._callstream("changegroup", roots=n)
275 275 return changegroupmod.unbundle10(self._decompress(f), 'UN')
276 276
277 277 def changegroupsubset(self, bases, heads, kind):
278 278 self.requirecap('changegroupsubset', _('look up remote changes'))
279 279 bases = encodelist(bases)
280 280 heads = encodelist(heads)
281 281 f = self._callstream("changegroupsubset",
282 282 bases=bases, heads=heads)
283 283 return changegroupmod.unbundle10(self._decompress(f), 'UN')
284 284
285 285 def getbundle(self, source, heads=None, common=None):
286 286 self.requirecap('getbundle', _('look up remote changes'))
287 287 opts = {}
288 288 if heads is not None:
289 289 opts['heads'] = encodelist(heads)
290 290 if common is not None:
291 291 opts['common'] = encodelist(common)
292 292 f = self._callstream("getbundle", **opts)
293 293 return changegroupmod.unbundle10(self._decompress(f), 'UN')
294 294
295 295 def unbundle(self, cg, heads, source):
296 296 '''Send cg (a readable file-like object representing the
297 297 changegroup to push, typically a chunkbuffer object) to the
298 298 remote server as a bundle. Return an integer indicating the
299 299 result of the push (see localrepository.addchangegroup()).'''
300 300
301 301 if heads != ['force'] and self.capable('unbundlehash'):
302 302 heads = encodelist(['hashed',
303 303 util.sha1(''.join(sorted(heads))).digest()])
304 304 else:
305 305 heads = encodelist(heads)
306 306
307 307 ret, output = self._callpush("unbundle", cg, heads=heads)
308 308 if ret == "":
309 309 raise error.ResponseError(
310 310 _('push failed:'), output)
311 311 try:
312 312 ret = int(ret)
313 313 except ValueError:
314 314 raise error.ResponseError(
315 315 _('push failed (unexpected response):'), ret)
316 316
317 317 for l in output.splitlines(True):
318 318 self.ui.status(_('remote: '), l)
319 319 return ret
320 320
321 321 def debugwireargs(self, one, two, three=None, four=None, five=None):
322 322 # don't pass optional arguments left at their default value
323 323 opts = {}
324 324 if three is not None:
325 325 opts['three'] = three
326 326 if four is not None:
327 327 opts['four'] = four
328 328 return self._call('debugwireargs', one=one, two=two, **opts)
329 329
330 330 # server side
331 331
332 332 class streamres(object):
333 333 def __init__(self, gen):
334 334 self.gen = gen
335 335
336 336 class pushres(object):
337 337 def __init__(self, res):
338 338 self.res = res
339 339
340 340 class pusherr(object):
341 341 def __init__(self, res):
342 342 self.res = res
343 343
344 344 class ooberror(object):
345 345 def __init__(self, message):
346 346 self.message = message
347 347
348 348 def dispatch(repo, proto, command):
349 349 func, spec = commands[command]
350 350 args = proto.getargs(spec)
351 351 return func(repo, proto, *args)
352 352
353 353 def options(cmd, keys, others):
354 354 opts = {}
355 355 for k in keys:
356 356 if k in others:
357 357 opts[k] = others[k]
358 358 del others[k]
359 359 if others:
360 360 sys.stderr.write("abort: %s got unexpected arguments %s\n"
361 361 % (cmd, ",".join(others)))
362 362 return opts
363 363
364 364 def batch(repo, proto, cmds, others):
365 365 res = []
366 366 for pair in cmds.split(';'):
367 367 op, args = pair.split(' ', 1)
368 368 vals = {}
369 369 for a in args.split(','):
370 370 if a:
371 371 n, v = a.split('=')
372 372 vals[n] = unescapearg(v)
373 373 func, spec = commands[op]
374 374 if spec:
375 375 keys = spec.split()
376 376 data = {}
377 377 for k in keys:
378 378 if k == '*':
379 379 star = {}
380 380 for key in vals.keys():
381 381 if key not in keys:
382 382 star[key] = vals[key]
383 383 data['*'] = star
384 384 else:
385 385 data[k] = vals[k]
386 386 result = func(repo, proto, *[data[k] for k in keys])
387 387 else:
388 388 result = func(repo, proto)
389 389 if isinstance(result, ooberror):
390 390 return result
391 391 res.append(escapearg(result))
392 392 return ';'.join(res)
393 393
394 394 def between(repo, proto, pairs):
395 395 pairs = [decodelist(p, '-') for p in pairs.split(" ")]
396 396 r = []
397 397 for b in repo.between(pairs):
398 398 r.append(encodelist(b) + "\n")
399 399 return "".join(r)
400 400
401 401 def branchmap(repo, proto):
402 402 branchmap = discovery.visiblebranchmap(repo)
403 403 heads = []
404 404 for branch, nodes in branchmap.iteritems():
405 405 branchname = urllib.quote(encoding.fromlocal(branch))
406 406 branchnodes = encodelist(nodes)
407 407 heads.append('%s %s' % (branchname, branchnodes))
408 408 return '\n'.join(heads)
409 409
410 410 def branches(repo, proto, nodes):
411 411 nodes = decodelist(nodes)
412 412 r = []
413 413 for b in repo.branches(nodes):
414 414 r.append(encodelist(b) + "\n")
415 415 return "".join(r)
416 416
417 417 def capabilities(repo, proto):
418 418 caps = ('lookup changegroupsubset branchmap pushkey known getbundle '
419 419 'unbundlehash batch').split()
420 420 if _allowstream(repo.ui):
421 421 if repo.ui.configbool('server', 'preferuncompressed', False):
422 422 caps.append('stream-preferred')
423 423 requiredformats = repo.requirements & repo.supportedformats
424 424 # if our local revlogs are just revlogv1, add 'stream' cap
425 425 if not requiredformats - set(('revlogv1',)):
426 426 caps.append('stream')
427 427 # otherwise, add 'streamreqs' detailing our local revlog format
428 428 else:
429 429 caps.append('streamreqs=%s' % ','.join(requiredformats))
430 430 caps.append('unbundle=%s' % ','.join(changegroupmod.bundlepriority))
431 431 caps.append('httpheader=1024')
432 432 return ' '.join(caps)
433 433
434 434 def changegroup(repo, proto, roots):
435 435 nodes = decodelist(roots)
436 436 cg = repo.changegroup(nodes, 'serve')
437 437 return streamres(proto.groupchunks(cg))
438 438
439 439 def changegroupsubset(repo, proto, bases, heads):
440 440 bases = decodelist(bases)
441 441 heads = decodelist(heads)
442 442 cg = repo.changegroupsubset(bases, heads, 'serve')
443 443 return streamres(proto.groupchunks(cg))
444 444
445 445 def debugwireargs(repo, proto, one, two, others):
446 446 # only accept optional args from the known set
447 447 opts = options('debugwireargs', ['three', 'four'], others)
448 448 return repo.debugwireargs(one, two, **opts)
449 449
450 450 def getbundle(repo, proto, others):
451 451 opts = options('getbundle', ['heads', 'common'], others)
452 452 for k, v in opts.iteritems():
453 453 opts[k] = decodelist(v)
454 454 cg = repo.getbundle('serve', **opts)
455 455 return streamres(proto.groupchunks(cg))
456 456
457 457 def heads(repo, proto):
458 458 h = discovery.visibleheads(repo)
459 459 return encodelist(h) + "\n"
460 460
461 461 def hello(repo, proto):
462 462 '''the hello command returns a set of lines describing various
463 463 interesting things about the server, in an RFC822-like format.
464 464 Currently the only one defined is "capabilities", which
465 465 consists of a line in the form:
466 466
467 467 capabilities: space separated list of tokens
468 468 '''
469 469 return "capabilities: %s\n" % (capabilities(repo, proto))
470 470
471 471 def listkeys(repo, proto, namespace):
472 472 d = repo.listkeys(encoding.tolocal(namespace)).items()
473 473 t = '\n'.join(['%s\t%s' % (encoding.fromlocal(k), encoding.fromlocal(v))
474 474 for k, v in d])
475 475 return t
476 476
477 477 def lookup(repo, proto, key):
478 478 try:
479 479 k = encoding.tolocal(key)
480 480 c = repo[k]
481 481 if c.phase() == phases.secret:
482 482 raise error.RepoLookupError(_("unknown revision '%s'") % k)
483 483 r = c.hex()
484 484 success = 1
485 485 except Exception, inst:
486 486 r = str(inst)
487 487 success = 0
488 488 return "%s %s\n" % (success, r)
489 489
490 490 def known(repo, proto, nodes, others):
491 491 return ''.join(b and "1" or "0" for b in repo.known(decodelist(nodes)))
492 492
493 493 def pushkey(repo, proto, namespace, key, old, new):
494 494 # compatibility with pre-1.8 clients which were accidentally
495 495 # sending raw binary nodes rather than utf-8-encoded hex
496 496 if len(new) == 20 and new.encode('string-escape') != new:
497 497 # looks like it could be a binary node
498 498 try:
499 499 new.decode('utf-8')
500 500 new = encoding.tolocal(new) # but cleanly decodes as UTF-8
501 501 except UnicodeDecodeError:
502 502 pass # binary, leave unmodified
503 503 else:
504 504 new = encoding.tolocal(new) # normal path
505 505
506 if util.safehasattr(proto, 'restore'):
507
508 proto.redirect()
509
510 try:
511 r = repo.pushkey(encoding.tolocal(namespace), encoding.tolocal(key),
512 encoding.tolocal(old), new) or False
513 except util.Abort:
514 r = False
515
516 output = proto.restore()
517
518 return '%s\n%s' % (int(r), output)
519
506 520 r = repo.pushkey(encoding.tolocal(namespace), encoding.tolocal(key),
507 521 encoding.tolocal(old), new)
508 522 return '%s\n' % int(r)
509 523
510 524 def _allowstream(ui):
511 525 return ui.configbool('server', 'uncompressed', True, untrusted=True)
512 526
513 527 def stream(repo, proto):
514 528 '''If the server supports streaming clone, it advertises the "stream"
515 529 capability with a value representing the version and flags of the repo
516 530 it is serving. Client checks to see if it understands the format.
517 531
518 532 The format is simple: the server writes out a line with the amount
519 533 of files, then the total amount of bytes to be transferred (separated
520 534 by a space). Then, for each file, the server first writes the filename
521 535 and filesize (separated by the null character), then the file contents.
522 536 '''
523 537
524 538 if not _allowstream(repo.ui):
525 539 return '1\n'
526 540
527 541 entries = []
528 542 total_bytes = 0
529 543 try:
530 544 # get consistent snapshot of repo, lock during scan
531 545 lock = repo.lock()
532 546 try:
533 547 repo.ui.debug('scanning\n')
534 548 for name, ename, size in repo.store.walk():
535 549 entries.append((name, size))
536 550 total_bytes += size
537 551 finally:
538 552 lock.release()
539 553 except error.LockError:
540 554 return '2\n' # error: 2
541 555
542 556 def streamer(repo, entries, total):
543 557 '''stream out all metadata files in repository.'''
544 558 yield '0\n' # success
545 559 repo.ui.debug('%d files, %d bytes to transfer\n' %
546 560 (len(entries), total_bytes))
547 561 yield '%d %d\n' % (len(entries), total_bytes)
548 562
549 563 sopener = repo.sopener
550 564 oldaudit = sopener.mustaudit
551 565 debugflag = repo.ui.debugflag
552 566 sopener.mustaudit = False
553 567
554 568 try:
555 569 for name, size in entries:
556 570 if debugflag:
557 571 repo.ui.debug('sending %s (%d bytes)\n' % (name, size))
558 572 # partially encode name over the wire for backwards compat
559 573 yield '%s\0%d\n' % (store.encodedir(name), size)
560 574 if size <= 65536:
561 575 fp = sopener(name)
562 576 try:
563 577 data = fp.read(size)
564 578 finally:
565 579 fp.close()
566 580 yield data
567 581 else:
568 582 for chunk in util.filechunkiter(sopener(name), limit=size):
569 583 yield chunk
570 584 # replace with "finally:" when support for python 2.4 has been dropped
571 585 except Exception:
572 586 sopener.mustaudit = oldaudit
573 587 raise
574 588 sopener.mustaudit = oldaudit
575 589
576 590 return streamres(streamer(repo, entries, total_bytes))
577 591
578 592 def unbundle(repo, proto, heads):
579 593 their_heads = decodelist(heads)
580 594
581 595 def check_heads():
582 596 heads = discovery.visibleheads(repo)
583 597 heads_hash = util.sha1(''.join(sorted(heads))).digest()
584 598 return (their_heads == ['force'] or their_heads == heads or
585 599 their_heads == ['hashed', heads_hash])
586 600
587 601 proto.redirect()
588 602
589 603 # fail early if possible
590 604 if not check_heads():
591 605 return pusherr('unsynced changes')
592 606
593 607 # write bundle data to temporary file because it can be big
594 608 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
595 609 fp = os.fdopen(fd, 'wb+')
596 610 r = 0
597 611 try:
598 612 proto.getfile(fp)
599 613 lock = repo.lock()
600 614 try:
601 615 if not check_heads():
602 616 # someone else committed/pushed/unbundled while we
603 617 # were transferring data
604 618 return pusherr('unsynced changes')
605 619
606 620 # push can proceed
607 621 fp.seek(0)
608 622 gen = changegroupmod.readbundle(fp, None)
609 623
610 624 try:
611 625 r = repo.addchangegroup(gen, 'serve', proto._client())
612 626 except util.Abort, inst:
613 627 sys.stderr.write("abort: %s\n" % inst)
614 628 finally:
615 629 lock.release()
616 630 return pushres(r)
617 631
618 632 finally:
619 633 fp.close()
620 634 os.unlink(tempname)
621 635
622 636 commands = {
623 637 'batch': (batch, 'cmds *'),
624 638 'between': (between, 'pairs'),
625 639 'branchmap': (branchmap, ''),
626 640 'branches': (branches, 'nodes'),
627 641 'capabilities': (capabilities, ''),
628 642 'changegroup': (changegroup, 'roots'),
629 643 'changegroupsubset': (changegroupsubset, 'bases heads'),
630 644 'debugwireargs': (debugwireargs, 'one two *'),
631 645 'getbundle': (getbundle, '*'),
632 646 'heads': (heads, ''),
633 647 'hello': (hello, ''),
634 648 'known': (known, 'nodes *'),
635 649 'listkeys': (listkeys, 'namespace'),
636 650 'lookup': (lookup, 'key'),
637 651 'pushkey': (pushkey, 'namespace key old new'),
638 652 'stream_out': (stream, ''),
639 653 'unbundle': (unbundle, 'heads'),
640 654 }
@@ -1,128 +1,163 b''
1 1 $ "$TESTDIR/hghave" killdaemons || exit 80
2 2
3 3 $ hg init test
4 4 $ cd test
5 5 $ echo a > a
6 6 $ hg ci -Ama
7 7 adding a
8 8 $ cd ..
9 9 $ hg clone test test2
10 10 updating to branch default
11 11 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
12 12 $ cd test2
13 13 $ echo a >> a
14 14 $ hg ci -mb
15 15 $ req() {
16 16 > hg serve -p $HGPORT -d --pid-file=hg.pid -E errors.log
17 17 > cat hg.pid >> $DAEMON_PIDS
18 18 > hg --cwd ../test2 push http://localhost:$HGPORT/
19 19 > exitstatus=$?
20 20 > "$TESTDIR/killdaemons.py" $DAEMON_PIDS
21 21 > echo % serve errors
22 22 > cat errors.log
23 23 > return $exitstatus
24 24 > }
25 25 $ cd ../test
26 26
27 27 expect ssl error
28 28
29 29 $ req
30 30 pushing to http://localhost:$HGPORT/
31 31 searching for changes
32 32 abort: HTTP Error 403: ssl required
33 33 % serve errors
34 34 [255]
35 35
36 36 expect authorization error
37 37
38 38 $ echo '[web]' > .hg/hgrc
39 39 $ echo 'push_ssl = false' >> .hg/hgrc
40 40 $ req
41 41 pushing to http://localhost:$HGPORT/
42 42 searching for changes
43 43 abort: authorization failed
44 44 % serve errors
45 45 [255]
46 46
47 47 expect authorization error: must have authorized user
48 48
49 49 $ echo 'allow_push = unperson' >> .hg/hgrc
50 50 $ req
51 51 pushing to http://localhost:$HGPORT/
52 52 searching for changes
53 53 abort: authorization failed
54 54 % serve errors
55 55 [255]
56 56
57 57 expect success
58 58
59 59 $ echo 'allow_push = *' >> .hg/hgrc
60 60 $ echo '[hooks]' >> .hg/hgrc
61 61 $ echo "changegroup = python \"$TESTDIR/printenv.py\" changegroup 0" >> .hg/hgrc
62 $ echo "pushkey = python \"$TESTDIR/printenv.py\" pushkey 0" >> .hg/hgrc
62 63 $ req
63 64 pushing to http://localhost:$HGPORT/
64 65 searching for changes
65 66 remote: adding changesets
66 67 remote: adding manifests
67 68 remote: adding file changes
68 69 remote: added 1 changesets with 1 changes to 1 files
69 70 remote: changegroup hook: HG_NODE=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_SOURCE=serve HG_URL=remote:http:*: (glob)
71 remote: pushkey hook: HG_KEY=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_NAMESPACE=phases HG_NEW=0 HG_OLD=1 HG_RET=1
70 72 % serve errors
71 73 $ hg rollback
72 74 repository tip rolled back to revision 0 (undo serve)
73 75
74 76 expect success, server lacks the httpheader capability
75 77
76 78 $ CAP=httpheader
77 79 $ . "$TESTDIR/notcapable"
78 80 $ req
79 81 pushing to http://localhost:$HGPORT/
80 82 searching for changes
81 83 remote: adding changesets
82 84 remote: adding manifests
83 85 remote: adding file changes
84 86 remote: added 1 changesets with 1 changes to 1 files
85 87 remote: changegroup hook: HG_NODE=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_SOURCE=serve HG_URL=remote:http:*: (glob)
88 remote: pushkey hook: HG_KEY=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_NAMESPACE=phases HG_NEW=0 HG_OLD=1 HG_RET=1
86 89 % serve errors
87 90 $ hg rollback
88 91 repository tip rolled back to revision 0 (undo serve)
89 92
90 93 expect success, server lacks the unbundlehash capability
91 94
92 95 $ CAP=unbundlehash
93 96 $ . "$TESTDIR/notcapable"
94 97 $ req
95 98 pushing to http://localhost:$HGPORT/
96 99 searching for changes
97 100 remote: adding changesets
98 101 remote: adding manifests
99 102 remote: adding file changes
100 103 remote: added 1 changesets with 1 changes to 1 files
101 104 remote: changegroup hook: HG_NODE=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_SOURCE=serve HG_URL=remote:http:*: (glob)
105 remote: pushkey hook: HG_KEY=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_NAMESPACE=phases HG_NEW=0 HG_OLD=1 HG_RET=1
102 106 % serve errors
103 107 $ hg rollback
104 108 repository tip rolled back to revision 0 (undo serve)
105 109
110 expect push success, phase change failure
111
112 $ echo '[web]' > .hg/hgrc
113 $ echo 'push_ssl = false' >> .hg/hgrc
114 $ echo 'allow_push = *' >> .hg/hgrc
115 $ echo '[hooks]' >> .hg/hgrc
116 $ echo 'prepushkey = python "$TESTDIR/printenv.py" prepushkey 1' >> .hg/hgrc
117 $ req
118 pushing to http://localhost:$HGPORT/
119 searching for changes
120 remote: adding changesets
121 remote: adding manifests
122 remote: adding file changes
123 remote: added 1 changesets with 1 changes to 1 files
124 remote: prepushkey hook: HG_KEY=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_NAMESPACE=phases HG_NEW=0 HG_OLD=1
125 updating ba677d0156c1 to public failed!
126 % serve errors
127
128 expect phase change success
129
130 $ echo 'prepushkey = python "$TESTDIR/printenv.py" prepushkey 0' >> .hg/hgrc
131 $ req
132 pushing to http://localhost:$HGPORT/
133 searching for changes
134 no changes found
135 remote: prepushkey hook: HG_KEY=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_NAMESPACE=phases HG_NEW=0 HG_OLD=1
136 % serve errors
137 [1]
138 $ hg rollback
139 repository tip rolled back to revision 0 (undo serve)
140
106 141 expect authorization error: all users denied
107 142
108 143 $ echo '[web]' > .hg/hgrc
109 144 $ echo 'push_ssl = false' >> .hg/hgrc
110 145 $ echo 'deny_push = *' >> .hg/hgrc
111 146 $ req
112 147 pushing to http://localhost:$HGPORT/
113 148 searching for changes
114 149 abort: authorization failed
115 150 % serve errors
116 151 [255]
117 152
118 153 expect authorization error: some users denied, users must be authenticated
119 154
120 155 $ echo 'deny_push = unperson' >> .hg/hgrc
121 156 $ req
122 157 pushing to http://localhost:$HGPORT/
123 158 searching for changes
124 159 abort: authorization failed
125 160 % serve errors
126 161 [255]
127 162
128 163 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now