##// END OF EJS Templates
ssl: set explicit symbol "!" to web.cacerts to disable SSL verification (BC)...
Yuya Nishihara -
r24290:b76d8c64 default
parent child Browse files
Show More
@@ -1,976 +1,976
1 1 # dispatch.py - command dispatching for mercurial
2 2 #
3 3 # Copyright 2005-2007 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 from i18n import _
9 9 import os, sys, atexit, signal, pdb, socket, errno, shlex, time, traceback, re
10 10 import difflib
11 11 import util, commands, hg, fancyopts, extensions, hook, error
12 12 import cmdutil, encoding
13 13 import ui as uimod
14 14
15 15 class request(object):
16 16 def __init__(self, args, ui=None, repo=None, fin=None, fout=None,
17 17 ferr=None):
18 18 self.args = args
19 19 self.ui = ui
20 20 self.repo = repo
21 21
22 22 # input/output/error streams
23 23 self.fin = fin
24 24 self.fout = fout
25 25 self.ferr = ferr
26 26
27 27 def run():
28 28 "run the command in sys.argv"
29 29 sys.exit((dispatch(request(sys.argv[1:])) or 0) & 255)
30 30
31 31 def _getsimilar(symbols, value):
32 32 sim = lambda x: difflib.SequenceMatcher(None, value, x).ratio()
33 33 # The cutoff for similarity here is pretty arbitrary. It should
34 34 # probably be investigated and tweaked.
35 35 return [s for s in symbols if sim(s) > 0.6]
36 36
37 37 def _formatparse(write, inst):
38 38 similar = []
39 39 if isinstance(inst, error.UnknownIdentifier):
40 40 # make sure to check fileset first, as revset can invoke fileset
41 41 similar = _getsimilar(inst.symbols, inst.function)
42 42 if len(inst.args) > 1:
43 43 write(_("hg: parse error at %s: %s\n") %
44 44 (inst.args[1], inst.args[0]))
45 45 if (inst.args[0][0] == ' '):
46 46 write(_("unexpected leading whitespace\n"))
47 47 else:
48 48 write(_("hg: parse error: %s\n") % inst.args[0])
49 49 if similar:
50 50 if len(similar) == 1:
51 51 write(_("(did you mean %r?)\n") % similar[0])
52 52 else:
53 53 ss = ", ".join(sorted(similar))
54 54 write(_("(did you mean one of %s?)\n") % ss)
55 55
56 56 def dispatch(req):
57 57 "run the command specified in req.args"
58 58 if req.ferr:
59 59 ferr = req.ferr
60 60 elif req.ui:
61 61 ferr = req.ui.ferr
62 62 else:
63 63 ferr = sys.stderr
64 64
65 65 try:
66 66 if not req.ui:
67 67 req.ui = uimod.ui()
68 68 if '--traceback' in req.args:
69 69 req.ui.setconfig('ui', 'traceback', 'on', '--traceback')
70 70
71 71 # set ui streams from the request
72 72 if req.fin:
73 73 req.ui.fin = req.fin
74 74 if req.fout:
75 75 req.ui.fout = req.fout
76 76 if req.ferr:
77 77 req.ui.ferr = req.ferr
78 78 except util.Abort, inst:
79 79 ferr.write(_("abort: %s\n") % inst)
80 80 if inst.hint:
81 81 ferr.write(_("(%s)\n") % inst.hint)
82 82 return -1
83 83 except error.ParseError, inst:
84 84 _formatparse(ferr.write, inst)
85 85 return -1
86 86
87 87 msg = ' '.join(' ' in a and repr(a) or a for a in req.args)
88 88 starttime = time.time()
89 89 ret = None
90 90 try:
91 91 ret = _runcatch(req)
92 92 return ret
93 93 finally:
94 94 duration = time.time() - starttime
95 95 req.ui.log("commandfinish", "%s exited %s after %0.2f seconds\n",
96 96 msg, ret or 0, duration)
97 97
98 98 def _runcatch(req):
99 99 def catchterm(*args):
100 100 raise error.SignalInterrupt
101 101
102 102 ui = req.ui
103 103 try:
104 104 for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM':
105 105 num = getattr(signal, name, None)
106 106 if num:
107 107 signal.signal(num, catchterm)
108 108 except ValueError:
109 109 pass # happens if called in a thread
110 110
111 111 try:
112 112 try:
113 113 debugger = 'pdb'
114 114 debugtrace = {
115 115 'pdb' : pdb.set_trace
116 116 }
117 117 debugmortem = {
118 118 'pdb' : pdb.post_mortem
119 119 }
120 120
121 121 # read --config before doing anything else
122 122 # (e.g. to change trust settings for reading .hg/hgrc)
123 123 cfgs = _parseconfig(req.ui, _earlygetopt(['--config'], req.args))
124 124
125 125 if req.repo:
126 126 # copy configs that were passed on the cmdline (--config) to
127 127 # the repo ui
128 128 for sec, name, val in cfgs:
129 129 req.repo.ui.setconfig(sec, name, val, source='--config')
130 130
131 131 # if we are in HGPLAIN mode, then disable custom debugging
132 132 debugger = ui.config("ui", "debugger")
133 133 debugmod = pdb
134 134 if not debugger or ui.plain():
135 135 debugger = 'pdb'
136 136 elif '--debugger' in req.args:
137 137 # This import can be slow for fancy debuggers, so only
138 138 # do it when absolutely necessary, i.e. when actual
139 139 # debugging has been requested
140 140 try:
141 141 debugmod = __import__(debugger)
142 142 except ImportError:
143 143 pass # Leave debugmod = pdb
144 144
145 145 debugtrace[debugger] = debugmod.set_trace
146 146 debugmortem[debugger] = debugmod.post_mortem
147 147
148 148 # enter the debugger before command execution
149 149 if '--debugger' in req.args:
150 150 ui.warn(_("entering debugger - "
151 151 "type c to continue starting hg or h for help\n"))
152 152
153 153 if (debugger != 'pdb' and
154 154 debugtrace[debugger] == debugtrace['pdb']):
155 155 ui.warn(_("%s debugger specified "
156 156 "but its module was not found\n") % debugger)
157 157
158 158 debugtrace[debugger]()
159 159 try:
160 160 return _dispatch(req)
161 161 finally:
162 162 ui.flush()
163 163 except: # re-raises
164 164 # enter the debugger when we hit an exception
165 165 if '--debugger' in req.args:
166 166 traceback.print_exc()
167 167 debugmortem[debugger](sys.exc_info()[2])
168 168 ui.traceback()
169 169 raise
170 170
171 171 # Global exception handling, alphabetically
172 172 # Mercurial-specific first, followed by built-in and library exceptions
173 173 except error.AmbiguousCommand, inst:
174 174 ui.warn(_("hg: command '%s' is ambiguous:\n %s\n") %
175 175 (inst.args[0], " ".join(inst.args[1])))
176 176 except error.ParseError, inst:
177 177 _formatparse(ui.warn, inst)
178 178 return -1
179 179 except error.LockHeld, inst:
180 180 if inst.errno == errno.ETIMEDOUT:
181 181 reason = _('timed out waiting for lock held by %s') % inst.locker
182 182 else:
183 183 reason = _('lock held by %s') % inst.locker
184 184 ui.warn(_("abort: %s: %s\n") % (inst.desc or inst.filename, reason))
185 185 except error.LockUnavailable, inst:
186 186 ui.warn(_("abort: could not lock %s: %s\n") %
187 187 (inst.desc or inst.filename, inst.strerror))
188 188 except error.CommandError, inst:
189 189 if inst.args[0]:
190 190 ui.warn(_("hg %s: %s\n") % (inst.args[0], inst.args[1]))
191 191 commands.help_(ui, inst.args[0], full=False, command=True)
192 192 else:
193 193 ui.warn(_("hg: %s\n") % inst.args[1])
194 194 commands.help_(ui, 'shortlist')
195 195 except error.OutOfBandError, inst:
196 196 ui.warn(_("abort: remote error:\n"))
197 197 ui.warn(''.join(inst.args))
198 198 except error.RepoError, inst:
199 199 ui.warn(_("abort: %s!\n") % inst)
200 200 if inst.hint:
201 201 ui.warn(_("(%s)\n") % inst.hint)
202 202 except error.ResponseError, inst:
203 203 ui.warn(_("abort: %s") % inst.args[0])
204 204 if not isinstance(inst.args[1], basestring):
205 205 ui.warn(" %r\n" % (inst.args[1],))
206 206 elif not inst.args[1]:
207 207 ui.warn(_(" empty string\n"))
208 208 else:
209 209 ui.warn("\n%r\n" % util.ellipsis(inst.args[1]))
210 210 except error.CensoredNodeError, inst:
211 211 ui.warn(_("abort: file censored %s!\n") % inst)
212 212 except error.RevlogError, inst:
213 213 ui.warn(_("abort: %s!\n") % inst)
214 214 except error.SignalInterrupt:
215 215 ui.warn(_("killed!\n"))
216 216 except error.UnknownCommand, inst:
217 217 ui.warn(_("hg: unknown command '%s'\n") % inst.args[0])
218 218 try:
219 219 # check if the command is in a disabled extension
220 220 # (but don't check for extensions themselves)
221 221 commands.help_(ui, inst.args[0], unknowncmd=True)
222 222 except error.UnknownCommand:
223 223 suggested = False
224 224 if len(inst.args) == 2:
225 225 sim = _getsimilar(inst.args[1], inst.args[0])
226 226 if sim:
227 227 ui.warn(_('(did you mean one of %s?)\n') %
228 228 ', '.join(sorted(sim)))
229 229 suggested = True
230 230 if not suggested:
231 231 commands.help_(ui, 'shortlist')
232 232 except error.InterventionRequired, inst:
233 233 ui.warn("%s\n" % inst)
234 234 return 1
235 235 except util.Abort, inst:
236 236 ui.warn(_("abort: %s\n") % inst)
237 237 if inst.hint:
238 238 ui.warn(_("(%s)\n") % inst.hint)
239 239 except ImportError, inst:
240 240 ui.warn(_("abort: %s!\n") % inst)
241 241 m = str(inst).split()[-1]
242 242 if m in "mpatch bdiff".split():
243 243 ui.warn(_("(did you forget to compile extensions?)\n"))
244 244 elif m in "zlib".split():
245 245 ui.warn(_("(is your Python install correct?)\n"))
246 246 except IOError, inst:
247 247 if util.safehasattr(inst, "code"):
248 248 ui.warn(_("abort: %s\n") % inst)
249 249 elif util.safehasattr(inst, "reason"):
250 250 try: # usually it is in the form (errno, strerror)
251 251 reason = inst.reason.args[1]
252 252 except (AttributeError, IndexError):
253 253 # it might be anything, for example a string
254 254 reason = inst.reason
255 255 if isinstance(reason, unicode):
256 256 # SSLError of Python 2.7.9 contains a unicode
257 257 reason = reason.encode(encoding.encoding, 'replace')
258 258 ui.warn(_("abort: error: %s\n") % reason)
259 259 elif (util.safehasattr(inst, "args")
260 260 and inst.args and inst.args[0] == errno.EPIPE):
261 261 if ui.debugflag:
262 262 ui.warn(_("broken pipe\n"))
263 263 elif getattr(inst, "strerror", None):
264 264 if getattr(inst, "filename", None):
265 265 ui.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
266 266 else:
267 267 ui.warn(_("abort: %s\n") % inst.strerror)
268 268 else:
269 269 raise
270 270 except OSError, inst:
271 271 if getattr(inst, "filename", None) is not None:
272 272 ui.warn(_("abort: %s: '%s'\n") % (inst.strerror, inst.filename))
273 273 else:
274 274 ui.warn(_("abort: %s\n") % inst.strerror)
275 275 except KeyboardInterrupt:
276 276 try:
277 277 ui.warn(_("interrupted!\n"))
278 278 except IOError, inst:
279 279 if inst.errno == errno.EPIPE:
280 280 if ui.debugflag:
281 281 ui.warn(_("\nbroken pipe\n"))
282 282 else:
283 283 raise
284 284 except MemoryError:
285 285 ui.warn(_("abort: out of memory\n"))
286 286 except SystemExit, inst:
287 287 # Commands shouldn't sys.exit directly, but give a return code.
288 288 # Just in case catch this and and pass exit code to caller.
289 289 return inst.code
290 290 except socket.error, inst:
291 291 ui.warn(_("abort: %s\n") % inst.args[-1])
292 292 except: # re-raises
293 293 myver = util.version()
294 294 # For compatibility checking, we discard the portion of the hg
295 295 # version after the + on the assumption that if a "normal
296 296 # user" is running a build with a + in it the packager
297 297 # probably built from fairly close to a tag and anyone with a
298 298 # 'make local' copy of hg (where the version number can be out
299 299 # of date) will be clueful enough to notice the implausible
300 300 # version number and try updating.
301 301 compare = myver.split('+')[0]
302 302 ct = tuplever(compare)
303 303 worst = None, ct, ''
304 304 for name, mod in extensions.extensions():
305 305 testedwith = getattr(mod, 'testedwith', '')
306 306 report = getattr(mod, 'buglink', _('the extension author.'))
307 307 if not testedwith.strip():
308 308 # We found an untested extension. It's likely the culprit.
309 309 worst = name, 'unknown', report
310 310 break
311 311
312 312 # Never blame on extensions bundled with Mercurial.
313 313 if testedwith == 'internal':
314 314 continue
315 315
316 316 tested = [tuplever(t) for t in testedwith.split()]
317 317 if ct in tested:
318 318 continue
319 319
320 320 lower = [t for t in tested if t < ct]
321 321 nearest = max(lower or tested)
322 322 if worst[0] is None or nearest < worst[1]:
323 323 worst = name, nearest, report
324 324 if worst[0] is not None:
325 325 name, testedwith, report = worst
326 326 if not isinstance(testedwith, str):
327 327 testedwith = '.'.join([str(c) for c in testedwith])
328 328 warning = (_('** Unknown exception encountered with '
329 329 'possibly-broken third-party extension %s\n'
330 330 '** which supports versions %s of Mercurial.\n'
331 331 '** Please disable %s and try your action again.\n'
332 332 '** If that fixes the bug please report it to %s\n')
333 333 % (name, testedwith, name, report))
334 334 else:
335 335 warning = (_("** unknown exception encountered, "
336 336 "please report by visiting\n") +
337 337 _("** http://mercurial.selenic.com/wiki/BugTracker\n"))
338 338 warning += ((_("** Python %s\n") % sys.version.replace('\n', '')) +
339 339 (_("** Mercurial Distributed SCM (version %s)\n") % myver) +
340 340 (_("** Extensions loaded: %s\n") %
341 341 ", ".join([x[0] for x in extensions.extensions()])))
342 342 ui.log("commandexception", "%s\n%s\n", warning, traceback.format_exc())
343 343 ui.warn(warning)
344 344 raise
345 345
346 346 return -1
347 347
348 348 def tuplever(v):
349 349 try:
350 350 # Assertion: tuplever is only used for extension compatibility
351 351 # checking. Otherwise, the discarding of extra version fields is
352 352 # incorrect.
353 353 return tuple([int(i) for i in v.split('.')[0:2]])
354 354 except ValueError:
355 355 return tuple()
356 356
357 357 def aliasargs(fn, givenargs):
358 358 args = getattr(fn, 'args', [])
359 359 if args:
360 360 cmd = ' '.join(map(util.shellquote, args))
361 361
362 362 nums = []
363 363 def replacer(m):
364 364 num = int(m.group(1)) - 1
365 365 nums.append(num)
366 366 if num < len(givenargs):
367 367 return givenargs[num]
368 368 raise util.Abort(_('too few arguments for command alias'))
369 369 cmd = re.sub(r'\$(\d+|\$)', replacer, cmd)
370 370 givenargs = [x for i, x in enumerate(givenargs)
371 371 if i not in nums]
372 372 args = shlex.split(cmd)
373 373 return args + givenargs
374 374
375 375 def aliasinterpolate(name, args, cmd):
376 376 '''interpolate args into cmd for shell aliases
377 377
378 378 This also handles $0, $@ and "$@".
379 379 '''
380 380 # util.interpolate can't deal with "$@" (with quotes) because it's only
381 381 # built to match prefix + patterns.
382 382 replacemap = dict(('$%d' % (i + 1), arg) for i, arg in enumerate(args))
383 383 replacemap['$0'] = name
384 384 replacemap['$$'] = '$'
385 385 replacemap['$@'] = ' '.join(args)
386 386 # Typical Unix shells interpolate "$@" (with quotes) as all the positional
387 387 # parameters, separated out into words. Emulate the same behavior here by
388 388 # quoting the arguments individually. POSIX shells will then typically
389 389 # tokenize each argument into exactly one word.
390 390 replacemap['"$@"'] = ' '.join(util.shellquote(arg) for arg in args)
391 391 # escape '\$' for regex
392 392 regex = '|'.join(replacemap.keys()).replace('$', r'\$')
393 393 r = re.compile(regex)
394 394 return r.sub(lambda x: replacemap[x.group()], cmd)
395 395
396 396 class cmdalias(object):
397 397 def __init__(self, name, definition, cmdtable):
398 398 self.name = self.cmd = name
399 399 self.cmdname = ''
400 400 self.definition = definition
401 401 self.fn = None
402 402 self.args = []
403 403 self.opts = []
404 404 self.help = ''
405 405 self.norepo = True
406 406 self.optionalrepo = False
407 407 self.badalias = None
408 408 self.unknowncmd = False
409 409
410 410 try:
411 411 aliases, entry = cmdutil.findcmd(self.name, cmdtable)
412 412 for alias, e in cmdtable.iteritems():
413 413 if e is entry:
414 414 self.cmd = alias
415 415 break
416 416 self.shadows = True
417 417 except error.UnknownCommand:
418 418 self.shadows = False
419 419
420 420 if not self.definition:
421 421 self.badalias = _("no definition for alias '%s'") % self.name
422 422 return
423 423
424 424 if self.definition.startswith('!'):
425 425 self.shell = True
426 426 def fn(ui, *args):
427 427 env = {'HG_ARGS': ' '.join((self.name,) + args)}
428 428 def _checkvar(m):
429 429 if m.groups()[0] == '$':
430 430 return m.group()
431 431 elif int(m.groups()[0]) <= len(args):
432 432 return m.group()
433 433 else:
434 434 ui.debug("No argument found for substitution "
435 435 "of %i variable in alias '%s' definition."
436 436 % (int(m.groups()[0]), self.name))
437 437 return ''
438 438 cmd = re.sub(r'\$(\d+|\$)', _checkvar, self.definition[1:])
439 439 cmd = aliasinterpolate(self.name, args, cmd)
440 440 return ui.system(cmd, environ=env)
441 441 self.fn = fn
442 442 return
443 443
444 444 try:
445 445 args = shlex.split(self.definition)
446 446 except ValueError, inst:
447 447 self.badalias = (_("error in definition for alias '%s': %s")
448 448 % (self.name, inst))
449 449 return
450 450 self.cmdname = cmd = args.pop(0)
451 451 args = map(util.expandpath, args)
452 452
453 453 for invalidarg in ("--cwd", "-R", "--repository", "--repo", "--config"):
454 454 if _earlygetopt([invalidarg], args):
455 455 self.badalias = (_("error in definition for alias '%s': %s may "
456 456 "only be given on the command line")
457 457 % (self.name, invalidarg))
458 458 return
459 459
460 460 try:
461 461 tableentry = cmdutil.findcmd(cmd, cmdtable, False)[1]
462 462 if len(tableentry) > 2:
463 463 self.fn, self.opts, self.help = tableentry
464 464 else:
465 465 self.fn, self.opts = tableentry
466 466
467 467 self.args = aliasargs(self.fn, args)
468 468 if cmd not in commands.norepo.split(' '):
469 469 self.norepo = False
470 470 if cmd in commands.optionalrepo.split(' '):
471 471 self.optionalrepo = True
472 472 if self.help.startswith("hg " + cmd):
473 473 # drop prefix in old-style help lines so hg shows the alias
474 474 self.help = self.help[4 + len(cmd):]
475 475 self.__doc__ = self.fn.__doc__
476 476
477 477 except error.UnknownCommand:
478 478 self.badalias = (_("alias '%s' resolves to unknown command '%s'")
479 479 % (self.name, cmd))
480 480 self.unknowncmd = True
481 481 except error.AmbiguousCommand:
482 482 self.badalias = (_("alias '%s' resolves to ambiguous command '%s'")
483 483 % (self.name, cmd))
484 484
485 485 def __call__(self, ui, *args, **opts):
486 486 if self.badalias:
487 487 hint = None
488 488 if self.unknowncmd:
489 489 try:
490 490 # check if the command is in a disabled extension
491 491 cmd, ext = extensions.disabledcmd(ui, self.cmdname)[:2]
492 492 hint = _("'%s' is provided by '%s' extension") % (cmd, ext)
493 493 except error.UnknownCommand:
494 494 pass
495 495 raise util.Abort(self.badalias, hint=hint)
496 496 if self.shadows:
497 497 ui.debug("alias '%s' shadows command '%s'\n" %
498 498 (self.name, self.cmdname))
499 499
500 500 if util.safehasattr(self, 'shell'):
501 501 return self.fn(ui, *args, **opts)
502 502 else:
503 503 try:
504 504 return util.checksignature(self.fn)(ui, *args, **opts)
505 505 except error.SignatureError:
506 506 args = ' '.join([self.cmdname] + self.args)
507 507 ui.debug("alias '%s' expands to '%s'\n" % (self.name, args))
508 508 raise
509 509
510 510 def addaliases(ui, cmdtable):
511 511 # aliases are processed after extensions have been loaded, so they
512 512 # may use extension commands. Aliases can also use other alias definitions,
513 513 # but only if they have been defined prior to the current definition.
514 514 for alias, definition in ui.configitems('alias'):
515 515 aliasdef = cmdalias(alias, definition, cmdtable)
516 516
517 517 try:
518 518 olddef = cmdtable[aliasdef.cmd][0]
519 519 if olddef.definition == aliasdef.definition:
520 520 continue
521 521 except (KeyError, AttributeError):
522 522 # definition might not exist or it might not be a cmdalias
523 523 pass
524 524
525 525 cmdtable[aliasdef.name] = (aliasdef, aliasdef.opts, aliasdef.help)
526 526 if aliasdef.norepo:
527 527 commands.norepo += ' %s' % alias
528 528 if aliasdef.optionalrepo:
529 529 commands.optionalrepo += ' %s' % alias
530 530
531 531 def _parse(ui, args):
532 532 options = {}
533 533 cmdoptions = {}
534 534
535 535 try:
536 536 args = fancyopts.fancyopts(args, commands.globalopts, options)
537 537 except fancyopts.getopt.GetoptError, inst:
538 538 raise error.CommandError(None, inst)
539 539
540 540 if args:
541 541 cmd, args = args[0], args[1:]
542 542 aliases, entry = cmdutil.findcmd(cmd, commands.table,
543 543 ui.configbool("ui", "strict"))
544 544 cmd = aliases[0]
545 545 args = aliasargs(entry[0], args)
546 546 defaults = ui.config("defaults", cmd)
547 547 if defaults:
548 548 args = map(util.expandpath, shlex.split(defaults)) + args
549 549 c = list(entry[1])
550 550 else:
551 551 cmd = None
552 552 c = []
553 553
554 554 # combine global options into local
555 555 for o in commands.globalopts:
556 556 c.append((o[0], o[1], options[o[1]], o[3]))
557 557
558 558 try:
559 559 args = fancyopts.fancyopts(args, c, cmdoptions, True)
560 560 except fancyopts.getopt.GetoptError, inst:
561 561 raise error.CommandError(cmd, inst)
562 562
563 563 # separate global options back out
564 564 for o in commands.globalopts:
565 565 n = o[1]
566 566 options[n] = cmdoptions[n]
567 567 del cmdoptions[n]
568 568
569 569 return (cmd, cmd and entry[0] or None, args, options, cmdoptions)
570 570
571 571 def _parseconfig(ui, config):
572 572 """parse the --config options from the command line"""
573 573 configs = []
574 574
575 575 for cfg in config:
576 576 try:
577 577 name, value = cfg.split('=', 1)
578 578 section, name = name.split('.', 1)
579 579 if not section or not name:
580 580 raise IndexError
581 581 ui.setconfig(section, name, value, '--config')
582 582 configs.append((section, name, value))
583 583 except (IndexError, ValueError):
584 584 raise util.Abort(_('malformed --config option: %r '
585 585 '(use --config section.name=value)') % cfg)
586 586
587 587 return configs
588 588
589 589 def _earlygetopt(aliases, args):
590 590 """Return list of values for an option (or aliases).
591 591
592 592 The values are listed in the order they appear in args.
593 593 The options and values are removed from args.
594 594
595 595 >>> args = ['x', '--cwd', 'foo', 'y']
596 596 >>> _earlygetopt(['--cwd'], args), args
597 597 (['foo'], ['x', 'y'])
598 598
599 599 >>> args = ['x', '--cwd=bar', 'y']
600 600 >>> _earlygetopt(['--cwd'], args), args
601 601 (['bar'], ['x', 'y'])
602 602
603 603 >>> args = ['x', '-R', 'foo', 'y']
604 604 >>> _earlygetopt(['-R'], args), args
605 605 (['foo'], ['x', 'y'])
606 606
607 607 >>> args = ['x', '-Rbar', 'y']
608 608 >>> _earlygetopt(['-R'], args), args
609 609 (['bar'], ['x', 'y'])
610 610 """
611 611 try:
612 612 argcount = args.index("--")
613 613 except ValueError:
614 614 argcount = len(args)
615 615 shortopts = [opt for opt in aliases if len(opt) == 2]
616 616 values = []
617 617 pos = 0
618 618 while pos < argcount:
619 619 fullarg = arg = args[pos]
620 620 equals = arg.find('=')
621 621 if equals > -1:
622 622 arg = arg[:equals]
623 623 if arg in aliases:
624 624 del args[pos]
625 625 if equals > -1:
626 626 values.append(fullarg[equals + 1:])
627 627 argcount -= 1
628 628 else:
629 629 if pos + 1 >= argcount:
630 630 # ignore and let getopt report an error if there is no value
631 631 break
632 632 values.append(args.pop(pos))
633 633 argcount -= 2
634 634 elif arg[:2] in shortopts:
635 635 # short option can have no following space, e.g. hg log -Rfoo
636 636 values.append(args.pop(pos)[2:])
637 637 argcount -= 1
638 638 else:
639 639 pos += 1
640 640 return values
641 641
642 642 def runcommand(lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions):
643 643 # run pre-hook, and abort if it fails
644 644 hook.hook(lui, repo, "pre-%s" % cmd, True, args=" ".join(fullargs),
645 645 pats=cmdpats, opts=cmdoptions)
646 646 ret = _runcommand(ui, options, cmd, d)
647 647 # run post-hook, passing command result
648 648 hook.hook(lui, repo, "post-%s" % cmd, False, args=" ".join(fullargs),
649 649 result=ret, pats=cmdpats, opts=cmdoptions)
650 650 return ret
651 651
652 652 def _getlocal(ui, rpath):
653 653 """Return (path, local ui object) for the given target path.
654 654
655 655 Takes paths in [cwd]/.hg/hgrc into account."
656 656 """
657 657 try:
658 658 wd = os.getcwd()
659 659 except OSError, e:
660 660 raise util.Abort(_("error getting current working directory: %s") %
661 661 e.strerror)
662 662 path = cmdutil.findrepo(wd) or ""
663 663 if not path:
664 664 lui = ui
665 665 else:
666 666 lui = ui.copy()
667 667 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
668 668
669 669 if rpath and rpath[-1]:
670 670 path = lui.expandpath(rpath[-1])
671 671 lui = ui.copy()
672 672 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
673 673
674 674 return path, lui
675 675
676 676 def _checkshellalias(lui, ui, args, precheck=True):
677 677 """Return the function to run the shell alias, if it is required
678 678
679 679 'precheck' is whether this function is invoked before adding
680 680 aliases or not.
681 681 """
682 682 options = {}
683 683
684 684 try:
685 685 args = fancyopts.fancyopts(args, commands.globalopts, options)
686 686 except fancyopts.getopt.GetoptError:
687 687 return
688 688
689 689 if not args:
690 690 return
691 691
692 692 if precheck:
693 693 strict = True
694 694 norepo = commands.norepo
695 695 optionalrepo = commands.optionalrepo
696 696 def restorecommands():
697 697 commands.norepo = norepo
698 698 commands.optionalrepo = optionalrepo
699 699 cmdtable = commands.table.copy()
700 700 addaliases(lui, cmdtable)
701 701 else:
702 702 strict = False
703 703 def restorecommands():
704 704 pass
705 705 cmdtable = commands.table
706 706
707 707 cmd = args[0]
708 708 try:
709 709 aliases, entry = cmdutil.findcmd(cmd, cmdtable, strict)
710 710 except (error.AmbiguousCommand, error.UnknownCommand):
711 711 restorecommands()
712 712 return
713 713
714 714 cmd = aliases[0]
715 715 fn = entry[0]
716 716
717 717 if cmd and util.safehasattr(fn, 'shell'):
718 718 d = lambda: fn(ui, *args[1:])
719 719 return lambda: runcommand(lui, None, cmd, args[:1], ui, options, d,
720 720 [], {})
721 721
722 722 restorecommands()
723 723
724 724 _loaded = set()
725 725 def _dispatch(req):
726 726 args = req.args
727 727 ui = req.ui
728 728
729 729 # check for cwd
730 730 cwd = _earlygetopt(['--cwd'], args)
731 731 if cwd:
732 732 os.chdir(cwd[-1])
733 733
734 734 rpath = _earlygetopt(["-R", "--repository", "--repo"], args)
735 735 path, lui = _getlocal(ui, rpath)
736 736
737 737 # Now that we're operating in the right directory/repository with
738 738 # the right config settings, check for shell aliases
739 739 shellaliasfn = _checkshellalias(lui, ui, args)
740 740 if shellaliasfn:
741 741 return shellaliasfn()
742 742
743 743 # Configure extensions in phases: uisetup, extsetup, cmdtable, and
744 744 # reposetup. Programs like TortoiseHg will call _dispatch several
745 745 # times so we keep track of configured extensions in _loaded.
746 746 extensions.loadall(lui)
747 747 exts = [ext for ext in extensions.extensions() if ext[0] not in _loaded]
748 748 # Propagate any changes to lui.__class__ by extensions
749 749 ui.__class__ = lui.__class__
750 750
751 751 # (uisetup and extsetup are handled in extensions.loadall)
752 752
753 753 for name, module in exts:
754 754 cmdtable = getattr(module, 'cmdtable', {})
755 755 overrides = [cmd for cmd in cmdtable if cmd in commands.table]
756 756 if overrides:
757 757 ui.warn(_("extension '%s' overrides commands: %s\n")
758 758 % (name, " ".join(overrides)))
759 759 commands.table.update(cmdtable)
760 760 _loaded.add(name)
761 761
762 762 # (reposetup is handled in hg.repository)
763 763
764 764 addaliases(lui, commands.table)
765 765
766 766 if not lui.configbool("ui", "strict"):
767 767 # All aliases and commands are completely defined, now.
768 768 # Check abbreviation/ambiguity of shell alias again, because shell
769 769 # alias may cause failure of "_parse" (see issue4355)
770 770 shellaliasfn = _checkshellalias(lui, ui, args, precheck=False)
771 771 if shellaliasfn:
772 772 return shellaliasfn()
773 773
774 774 # check for fallback encoding
775 775 fallback = lui.config('ui', 'fallbackencoding')
776 776 if fallback:
777 777 encoding.fallbackencoding = fallback
778 778
779 779 fullargs = args
780 780 cmd, func, args, options, cmdoptions = _parse(lui, args)
781 781
782 782 if options["config"]:
783 783 raise util.Abort(_("option --config may not be abbreviated!"))
784 784 if options["cwd"]:
785 785 raise util.Abort(_("option --cwd may not be abbreviated!"))
786 786 if options["repository"]:
787 787 raise util.Abort(_(
788 788 "option -R has to be separated from other options (e.g. not -qR) "
789 789 "and --repository may only be abbreviated as --repo!"))
790 790
791 791 if options["encoding"]:
792 792 encoding.encoding = options["encoding"]
793 793 if options["encodingmode"]:
794 794 encoding.encodingmode = options["encodingmode"]
795 795 if options["time"]:
796 796 def get_times():
797 797 t = os.times()
798 798 if t[4] == 0.0: # Windows leaves this as zero, so use time.clock()
799 799 t = (t[0], t[1], t[2], t[3], time.clock())
800 800 return t
801 801 s = get_times()
802 802 def print_time():
803 803 t = get_times()
804 804 ui.warn(_("time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
805 805 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
806 806 atexit.register(print_time)
807 807
808 808 uis = set([ui, lui])
809 809
810 810 if req.repo:
811 811 uis.add(req.repo.ui)
812 812
813 813 if options['verbose'] or options['debug'] or options['quiet']:
814 814 for opt in ('verbose', 'debug', 'quiet'):
815 815 val = str(bool(options[opt]))
816 816 for ui_ in uis:
817 817 ui_.setconfig('ui', opt, val, '--' + opt)
818 818
819 819 if options['traceback']:
820 820 for ui_ in uis:
821 821 ui_.setconfig('ui', 'traceback', 'on', '--traceback')
822 822
823 823 if options['noninteractive']:
824 824 for ui_ in uis:
825 825 ui_.setconfig('ui', 'interactive', 'off', '-y')
826 826
827 827 if cmdoptions.get('insecure', False):
828 828 for ui_ in uis:
829 ui_.setconfig('web', 'cacerts', '', '--insecure')
829 ui_.setconfig('web', 'cacerts', '!', '--insecure')
830 830
831 831 if options['version']:
832 832 return commands.version_(ui)
833 833 if options['help']:
834 834 return commands.help_(ui, cmd, command=True)
835 835 elif not cmd:
836 836 return commands.help_(ui, 'shortlist')
837 837
838 838 repo = None
839 839 cmdpats = args[:]
840 840 if cmd not in commands.norepo.split():
841 841 # use the repo from the request only if we don't have -R
842 842 if not rpath and not cwd:
843 843 repo = req.repo
844 844
845 845 if repo:
846 846 # set the descriptors of the repo ui to those of ui
847 847 repo.ui.fin = ui.fin
848 848 repo.ui.fout = ui.fout
849 849 repo.ui.ferr = ui.ferr
850 850 else:
851 851 try:
852 852 repo = hg.repository(ui, path=path)
853 853 if not repo.local():
854 854 raise util.Abort(_("repository '%s' is not local") % path)
855 855 repo.ui.setconfig("bundle", "mainreporoot", repo.root, 'repo')
856 856 except error.RequirementError:
857 857 raise
858 858 except error.RepoError:
859 859 if cmd not in commands.optionalrepo.split():
860 860 if (cmd in commands.inferrepo.split() and
861 861 args and not path): # try to infer -R from command args
862 862 repos = map(cmdutil.findrepo, args)
863 863 guess = repos[0]
864 864 if guess and repos.count(guess) == len(repos):
865 865 req.args = ['--repository', guess] + fullargs
866 866 return _dispatch(req)
867 867 if not path:
868 868 raise error.RepoError(_("no repository found in '%s'"
869 869 " (.hg not found)")
870 870 % os.getcwd())
871 871 raise
872 872 if repo:
873 873 ui = repo.ui
874 874 if options['hidden']:
875 875 repo = repo.unfiltered()
876 876 args.insert(0, repo)
877 877 elif rpath:
878 878 ui.warn(_("warning: --repository ignored\n"))
879 879
880 880 msg = ' '.join(' ' in a and repr(a) or a for a in fullargs)
881 881 ui.log("command", '%s\n', msg)
882 882 d = lambda: util.checksignature(func)(ui, *args, **cmdoptions)
883 883 try:
884 884 return runcommand(lui, repo, cmd, fullargs, ui, options, d,
885 885 cmdpats, cmdoptions)
886 886 finally:
887 887 if repo and repo != req.repo:
888 888 repo.close()
889 889
890 890 def lsprofile(ui, func, fp):
891 891 format = ui.config('profiling', 'format', default='text')
892 892 field = ui.config('profiling', 'sort', default='inlinetime')
893 893 limit = ui.configint('profiling', 'limit', default=30)
894 894 climit = ui.configint('profiling', 'nested', default=5)
895 895
896 896 if format not in ['text', 'kcachegrind']:
897 897 ui.warn(_("unrecognized profiling format '%s'"
898 898 " - Ignored\n") % format)
899 899 format = 'text'
900 900
901 901 try:
902 902 from mercurial import lsprof
903 903 except ImportError:
904 904 raise util.Abort(_(
905 905 'lsprof not available - install from '
906 906 'http://codespeak.net/svn/user/arigo/hack/misc/lsprof/'))
907 907 p = lsprof.Profiler()
908 908 p.enable(subcalls=True)
909 909 try:
910 910 return func()
911 911 finally:
912 912 p.disable()
913 913
914 914 if format == 'kcachegrind':
915 915 import lsprofcalltree
916 916 calltree = lsprofcalltree.KCacheGrind(p)
917 917 calltree.output(fp)
918 918 else:
919 919 # format == 'text'
920 920 stats = lsprof.Stats(p.getstats())
921 921 stats.sort(field)
922 922 stats.pprint(limit=limit, file=fp, climit=climit)
923 923
924 924 def statprofile(ui, func, fp):
925 925 try:
926 926 import statprof
927 927 except ImportError:
928 928 raise util.Abort(_(
929 929 'statprof not available - install using "easy_install statprof"'))
930 930
931 931 freq = ui.configint('profiling', 'freq', default=1000)
932 932 if freq > 0:
933 933 statprof.reset(freq)
934 934 else:
935 935 ui.warn(_("invalid sampling frequency '%s' - ignoring\n") % freq)
936 936
937 937 statprof.start()
938 938 try:
939 939 return func()
940 940 finally:
941 941 statprof.stop()
942 942 statprof.display(fp)
943 943
944 944 def _runcommand(ui, options, cmd, cmdfunc):
945 945 def checkargs():
946 946 try:
947 947 return cmdfunc()
948 948 except error.SignatureError:
949 949 raise error.CommandError(cmd, _("invalid arguments"))
950 950
951 951 if options['profile']:
952 952 profiler = os.getenv('HGPROF')
953 953 if profiler is None:
954 954 profiler = ui.config('profiling', 'type', default='ls')
955 955 if profiler not in ('ls', 'stat'):
956 956 ui.warn(_("unrecognized profiler '%s' - ignored\n") % profiler)
957 957 profiler = 'ls'
958 958
959 959 output = ui.config('profiling', 'output')
960 960
961 961 if output:
962 962 path = ui.expandpath(output)
963 963 fp = open(path, 'wb')
964 964 else:
965 965 fp = sys.stderr
966 966
967 967 try:
968 968 if profiler == 'ls':
969 969 return lsprofile(ui, checkargs, fp)
970 970 else:
971 971 return statprofile(ui, checkargs, fp)
972 972 finally:
973 973 if output:
974 974 fp.close()
975 975 else:
976 976 return checkargs()
@@ -1,678 +1,680
1 1 # hg.py - repository classes for mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
5 5 #
6 6 # This software may be used and distributed according to the terms of the
7 7 # GNU General Public License version 2 or any later version.
8 8
9 9 from i18n import _
10 10 from lock import release
11 11 from node import nullid
12 12
13 13 import localrepo, bundlerepo, unionrepo, httppeer, sshpeer, statichttprepo
14 14 import bookmarks, lock, util, extensions, error, node, scmutil, phases, url
15 15 import cmdutil, discovery, repoview, exchange
16 16 import ui as uimod
17 17 import merge as mergemod
18 18 import verify as verifymod
19 19 import errno, os, shutil
20 20
21 21 def _local(path):
22 22 path = util.expandpath(util.urllocalpath(path))
23 23 return (os.path.isfile(path) and bundlerepo or localrepo)
24 24
25 25 def addbranchrevs(lrepo, other, branches, revs):
26 26 peer = other.peer() # a courtesy to callers using a localrepo for other
27 27 hashbranch, branches = branches
28 28 if not hashbranch and not branches:
29 29 x = revs or None
30 30 if util.safehasattr(revs, 'first'):
31 31 y = revs.first()
32 32 elif revs:
33 33 y = revs[0]
34 34 else:
35 35 y = None
36 36 return x, y
37 37 revs = revs and list(revs) or []
38 38 if not peer.capable('branchmap'):
39 39 if branches:
40 40 raise util.Abort(_("remote branch lookup not supported"))
41 41 revs.append(hashbranch)
42 42 return revs, revs[0]
43 43 branchmap = peer.branchmap()
44 44
45 45 def primary(branch):
46 46 if branch == '.':
47 47 if not lrepo:
48 48 raise util.Abort(_("dirstate branch not accessible"))
49 49 branch = lrepo.dirstate.branch()
50 50 if branch in branchmap:
51 51 revs.extend(node.hex(r) for r in reversed(branchmap[branch]))
52 52 return True
53 53 else:
54 54 return False
55 55
56 56 for branch in branches:
57 57 if not primary(branch):
58 58 raise error.RepoLookupError(_("unknown branch '%s'") % branch)
59 59 if hashbranch:
60 60 if not primary(hashbranch):
61 61 revs.append(hashbranch)
62 62 return revs, revs[0]
63 63
64 64 def parseurl(path, branches=None):
65 65 '''parse url#branch, returning (url, (branch, branches))'''
66 66
67 67 u = util.url(path)
68 68 branch = None
69 69 if u.fragment:
70 70 branch = u.fragment
71 71 u.fragment = None
72 72 return str(u), (branch, branches or [])
73 73
74 74 schemes = {
75 75 'bundle': bundlerepo,
76 76 'union': unionrepo,
77 77 'file': _local,
78 78 'http': httppeer,
79 79 'https': httppeer,
80 80 'ssh': sshpeer,
81 81 'static-http': statichttprepo,
82 82 }
83 83
84 84 def _peerlookup(path):
85 85 u = util.url(path)
86 86 scheme = u.scheme or 'file'
87 87 thing = schemes.get(scheme) or schemes['file']
88 88 try:
89 89 return thing(path)
90 90 except TypeError:
91 91 return thing
92 92
93 93 def islocal(repo):
94 94 '''return true if repo (or path pointing to repo) is local'''
95 95 if isinstance(repo, str):
96 96 try:
97 97 return _peerlookup(repo).islocal(repo)
98 98 except AttributeError:
99 99 return False
100 100 return repo.local()
101 101
102 102 def openpath(ui, path):
103 103 '''open path with open if local, url.open if remote'''
104 104 pathurl = util.url(path, parsequery=False, parsefragment=False)
105 105 if pathurl.islocal():
106 106 return util.posixfile(pathurl.localpath(), 'rb')
107 107 else:
108 108 return url.open(ui, path)
109 109
110 110 # a list of (ui, repo) functions called for wire peer initialization
111 111 wirepeersetupfuncs = []
112 112
113 113 def _peerorrepo(ui, path, create=False):
114 114 """return a repository object for the specified path"""
115 115 obj = _peerlookup(path).instance(ui, path, create)
116 116 ui = getattr(obj, "ui", ui)
117 117 for name, module in extensions.extensions(ui):
118 118 hook = getattr(module, 'reposetup', None)
119 119 if hook:
120 120 hook(ui, obj)
121 121 if not obj.local():
122 122 for f in wirepeersetupfuncs:
123 123 f(ui, obj)
124 124 return obj
125 125
126 126 def repository(ui, path='', create=False):
127 127 """return a repository object for the specified path"""
128 128 peer = _peerorrepo(ui, path, create)
129 129 repo = peer.local()
130 130 if not repo:
131 131 raise util.Abort(_("repository '%s' is not local") %
132 132 (path or peer.url()))
133 133 return repo.filtered('visible')
134 134
135 135 def peer(uiorrepo, opts, path, create=False):
136 136 '''return a repository peer for the specified path'''
137 137 rui = remoteui(uiorrepo, opts)
138 138 return _peerorrepo(rui, path, create).peer()
139 139
140 140 def defaultdest(source):
141 141 '''return default destination of clone if none is given
142 142
143 143 >>> defaultdest('foo')
144 144 'foo'
145 145 >>> defaultdest('/foo/bar')
146 146 'bar'
147 147 >>> defaultdest('/')
148 148 ''
149 149 >>> defaultdest('')
150 150 ''
151 151 >>> defaultdest('http://example.org/')
152 152 ''
153 153 >>> defaultdest('http://example.org/foo/')
154 154 'foo'
155 155 '''
156 156 path = util.url(source).path
157 157 if not path:
158 158 return ''
159 159 return os.path.basename(os.path.normpath(path))
160 160
161 161 def share(ui, source, dest=None, update=True, bookmarks=True):
162 162 '''create a shared repository'''
163 163
164 164 if not islocal(source):
165 165 raise util.Abort(_('can only share local repositories'))
166 166
167 167 if not dest:
168 168 dest = defaultdest(source)
169 169 else:
170 170 dest = ui.expandpath(dest)
171 171
172 172 if isinstance(source, str):
173 173 origsource = ui.expandpath(source)
174 174 source, branches = parseurl(origsource)
175 175 srcrepo = repository(ui, source)
176 176 rev, checkout = addbranchrevs(srcrepo, srcrepo, branches, None)
177 177 else:
178 178 srcrepo = source.local()
179 179 origsource = source = srcrepo.url()
180 180 checkout = None
181 181
182 182 sharedpath = srcrepo.sharedpath # if our source is already sharing
183 183
184 184 destwvfs = scmutil.vfs(dest, realpath=True)
185 185 destvfs = scmutil.vfs(os.path.join(destwvfs.base, '.hg'), realpath=True)
186 186
187 187 if destvfs.lexists():
188 188 raise util.Abort(_('destination already exists'))
189 189
190 190 if not destwvfs.isdir():
191 191 destwvfs.mkdir()
192 192 destvfs.makedir()
193 193
194 194 requirements = ''
195 195 try:
196 196 requirements = srcrepo.vfs.read('requires')
197 197 except IOError, inst:
198 198 if inst.errno != errno.ENOENT:
199 199 raise
200 200
201 201 requirements += 'shared\n'
202 202 destvfs.write('requires', requirements)
203 203 destvfs.write('sharedpath', sharedpath)
204 204
205 205 r = repository(ui, destwvfs.base)
206 206
207 207 default = srcrepo.ui.config('paths', 'default')
208 208 if default:
209 209 fp = r.vfs("hgrc", "w", text=True)
210 210 fp.write("[paths]\n")
211 211 fp.write("default = %s\n" % default)
212 212 fp.close()
213 213
214 214 if update:
215 215 r.ui.status(_("updating working directory\n"))
216 216 if update is not True:
217 217 checkout = update
218 218 for test in (checkout, 'default', 'tip'):
219 219 if test is None:
220 220 continue
221 221 try:
222 222 uprev = r.lookup(test)
223 223 break
224 224 except error.RepoLookupError:
225 225 continue
226 226 _update(r, uprev)
227 227
228 228 if bookmarks:
229 229 fp = r.vfs('shared', 'w')
230 230 fp.write('bookmarks\n')
231 231 fp.close()
232 232
233 233 def copystore(ui, srcrepo, destpath):
234 234 '''copy files from store of srcrepo in destpath
235 235
236 236 returns destlock
237 237 '''
238 238 destlock = None
239 239 try:
240 240 hardlink = None
241 241 num = 0
242 242 srcpublishing = srcrepo.ui.configbool('phases', 'publish', True)
243 243 srcvfs = scmutil.vfs(srcrepo.sharedpath)
244 244 dstvfs = scmutil.vfs(destpath)
245 245 for f in srcrepo.store.copylist():
246 246 if srcpublishing and f.endswith('phaseroots'):
247 247 continue
248 248 dstbase = os.path.dirname(f)
249 249 if dstbase and not dstvfs.exists(dstbase):
250 250 dstvfs.mkdir(dstbase)
251 251 if srcvfs.exists(f):
252 252 if f.endswith('data'):
253 253 # 'dstbase' may be empty (e.g. revlog format 0)
254 254 lockfile = os.path.join(dstbase, "lock")
255 255 # lock to avoid premature writing to the target
256 256 destlock = lock.lock(dstvfs, lockfile)
257 257 hardlink, n = util.copyfiles(srcvfs.join(f), dstvfs.join(f),
258 258 hardlink)
259 259 num += n
260 260 if hardlink:
261 261 ui.debug("linked %d files\n" % num)
262 262 else:
263 263 ui.debug("copied %d files\n" % num)
264 264 return destlock
265 265 except: # re-raises
266 266 release(destlock)
267 267 raise
268 268
269 269 def clone(ui, peeropts, source, dest=None, pull=False, rev=None,
270 270 update=True, stream=False, branch=None):
271 271 """Make a copy of an existing repository.
272 272
273 273 Create a copy of an existing repository in a new directory. The
274 274 source and destination are URLs, as passed to the repository
275 275 function. Returns a pair of repository peers, the source and
276 276 newly created destination.
277 277
278 278 The location of the source is added to the new repository's
279 279 .hg/hgrc file, as the default to be used for future pulls and
280 280 pushes.
281 281
282 282 If an exception is raised, the partly cloned/updated destination
283 283 repository will be deleted.
284 284
285 285 Arguments:
286 286
287 287 source: repository object or URL
288 288
289 289 dest: URL of destination repository to create (defaults to base
290 290 name of source repository)
291 291
292 292 pull: always pull from source repository, even in local case or if the
293 293 server prefers streaming
294 294
295 295 stream: stream raw data uncompressed from repository (fast over
296 296 LAN, slow over WAN)
297 297
298 298 rev: revision to clone up to (implies pull=True)
299 299
300 300 update: update working directory after clone completes, if
301 301 destination is local repository (True means update to default rev,
302 302 anything else is treated as a revision)
303 303
304 304 branch: branches to clone
305 305 """
306 306
307 307 if isinstance(source, str):
308 308 origsource = ui.expandpath(source)
309 309 source, branch = parseurl(origsource, branch)
310 310 srcpeer = peer(ui, peeropts, source)
311 311 else:
312 312 srcpeer = source.peer() # in case we were called with a localrepo
313 313 branch = (None, branch or [])
314 314 origsource = source = srcpeer.url()
315 315 rev, checkout = addbranchrevs(srcpeer, srcpeer, branch, rev)
316 316
317 317 if dest is None:
318 318 dest = defaultdest(source)
319 319 if dest:
320 320 ui.status(_("destination directory: %s\n") % dest)
321 321 else:
322 322 dest = ui.expandpath(dest)
323 323
324 324 dest = util.urllocalpath(dest)
325 325 source = util.urllocalpath(source)
326 326
327 327 if not dest:
328 328 raise util.Abort(_("empty destination path is not valid"))
329 329
330 330 destvfs = scmutil.vfs(dest, expandpath=True)
331 331 if destvfs.lexists():
332 332 if not destvfs.isdir():
333 333 raise util.Abort(_("destination '%s' already exists") % dest)
334 334 elif destvfs.listdir():
335 335 raise util.Abort(_("destination '%s' is not empty") % dest)
336 336
337 337 srclock = destlock = cleandir = None
338 338 srcrepo = srcpeer.local()
339 339 try:
340 340 abspath = origsource
341 341 if islocal(origsource):
342 342 abspath = os.path.abspath(util.urllocalpath(origsource))
343 343
344 344 if islocal(dest):
345 345 cleandir = dest
346 346
347 347 copy = False
348 348 if (srcrepo and srcrepo.cancopy() and islocal(dest)
349 349 and not phases.hassecret(srcrepo)):
350 350 copy = not pull and not rev
351 351
352 352 if copy:
353 353 try:
354 354 # we use a lock here because if we race with commit, we
355 355 # can end up with extra data in the cloned revlogs that's
356 356 # not pointed to by changesets, thus causing verify to
357 357 # fail
358 358 srclock = srcrepo.lock(wait=False)
359 359 except error.LockError:
360 360 copy = False
361 361
362 362 if copy:
363 363 srcrepo.hook('preoutgoing', throw=True, source='clone')
364 364 hgdir = os.path.realpath(os.path.join(dest, ".hg"))
365 365 if not os.path.exists(dest):
366 366 os.mkdir(dest)
367 367 else:
368 368 # only clean up directories we create ourselves
369 369 cleandir = hgdir
370 370 try:
371 371 destpath = hgdir
372 372 util.makedir(destpath, notindexed=True)
373 373 except OSError, inst:
374 374 if inst.errno == errno.EEXIST:
375 375 cleandir = None
376 376 raise util.Abort(_("destination '%s' already exists")
377 377 % dest)
378 378 raise
379 379
380 380 destlock = copystore(ui, srcrepo, destpath)
381 381 # copy bookmarks over
382 382 srcbookmarks = srcrepo.join('bookmarks')
383 383 dstbookmarks = os.path.join(destpath, 'bookmarks')
384 384 if os.path.exists(srcbookmarks):
385 385 util.copyfile(srcbookmarks, dstbookmarks)
386 386
387 387 # Recomputing branch cache might be slow on big repos,
388 388 # so just copy it
389 389 def copybranchcache(fname):
390 390 srcbranchcache = srcrepo.join('cache/%s' % fname)
391 391 dstbranchcache = os.path.join(dstcachedir, fname)
392 392 if os.path.exists(srcbranchcache):
393 393 if not os.path.exists(dstcachedir):
394 394 os.mkdir(dstcachedir)
395 395 util.copyfile(srcbranchcache, dstbranchcache)
396 396
397 397 dstcachedir = os.path.join(destpath, 'cache')
398 398 # In local clones we're copying all nodes, not just served
399 399 # ones. Therefore copy all branch caches over.
400 400 copybranchcache('branch2')
401 401 for cachename in repoview.filtertable:
402 402 copybranchcache('branch2-%s' % cachename)
403 403
404 404 # we need to re-init the repo after manually copying the data
405 405 # into it
406 406 destpeer = peer(srcrepo, peeropts, dest)
407 407 srcrepo.hook('outgoing', source='clone',
408 408 node=node.hex(node.nullid))
409 409 else:
410 410 try:
411 411 destpeer = peer(srcrepo or ui, peeropts, dest, create=True)
412 412 # only pass ui when no srcrepo
413 413 except OSError, inst:
414 414 if inst.errno == errno.EEXIST:
415 415 cleandir = None
416 416 raise util.Abort(_("destination '%s' already exists")
417 417 % dest)
418 418 raise
419 419
420 420 revs = None
421 421 if rev:
422 422 if not srcpeer.capable('lookup'):
423 423 raise util.Abort(_("src repository does not support "
424 424 "revision lookup and so doesn't "
425 425 "support clone by revision"))
426 426 revs = [srcpeer.lookup(r) for r in rev]
427 427 checkout = revs[0]
428 428 if destpeer.local():
429 429 if not stream:
430 430 if pull:
431 431 stream = False
432 432 else:
433 433 stream = None
434 434 destpeer.local().clone(srcpeer, heads=revs, stream=stream)
435 435 elif srcrepo:
436 436 exchange.push(srcrepo, destpeer, revs=revs,
437 437 bookmarks=srcrepo._bookmarks.keys())
438 438 else:
439 439 raise util.Abort(_("clone from remote to remote not supported"))
440 440
441 441 cleandir = None
442 442
443 443 destrepo = destpeer.local()
444 444 if destrepo:
445 445 template = uimod.samplehgrcs['cloned']
446 446 fp = destrepo.vfs("hgrc", "w", text=True)
447 447 u = util.url(abspath)
448 448 u.passwd = None
449 449 defaulturl = str(u)
450 450 fp.write(template % defaulturl)
451 451 fp.close()
452 452
453 453 destrepo.ui.setconfig('paths', 'default', defaulturl, 'clone')
454 454
455 455 if update:
456 456 if update is not True:
457 457 checkout = srcpeer.lookup(update)
458 458 uprev = None
459 459 status = None
460 460 if checkout is not None:
461 461 try:
462 462 uprev = destrepo.lookup(checkout)
463 463 except error.RepoLookupError:
464 464 pass
465 465 if uprev is None:
466 466 try:
467 467 uprev = destrepo._bookmarks['@']
468 468 update = '@'
469 469 bn = destrepo[uprev].branch()
470 470 if bn == 'default':
471 471 status = _("updating to bookmark @\n")
472 472 else:
473 473 status = (_("updating to bookmark @ on branch %s\n")
474 474 % bn)
475 475 except KeyError:
476 476 try:
477 477 uprev = destrepo.branchtip('default')
478 478 except error.RepoLookupError:
479 479 uprev = destrepo.lookup('tip')
480 480 if not status:
481 481 bn = destrepo[uprev].branch()
482 482 status = _("updating to branch %s\n") % bn
483 483 destrepo.ui.status(status)
484 484 _update(destrepo, uprev)
485 485 if update in destrepo._bookmarks:
486 486 bookmarks.setcurrent(destrepo, update)
487 487 finally:
488 488 release(srclock, destlock)
489 489 if cleandir is not None:
490 490 shutil.rmtree(cleandir, True)
491 491 if srcpeer is not None:
492 492 srcpeer.close()
493 493 return srcpeer, destpeer
494 494
495 495 def _showstats(repo, stats):
496 496 repo.ui.status(_("%d files updated, %d files merged, "
497 497 "%d files removed, %d files unresolved\n") % stats)
498 498
499 499 def updaterepo(repo, node, overwrite):
500 500 """Update the working directory to node.
501 501
502 502 When overwrite is set, changes are clobbered, merged else
503 503
504 504 returns stats (see pydoc mercurial.merge.applyupdates)"""
505 505 return mergemod.update(repo, node, False, overwrite, None,
506 506 labels=['working copy', 'destination'])
507 507
508 508 def update(repo, node):
509 509 """update the working directory to node, merging linear changes"""
510 510 stats = updaterepo(repo, node, False)
511 511 _showstats(repo, stats)
512 512 if stats[3]:
513 513 repo.ui.status(_("use 'hg resolve' to retry unresolved file merges\n"))
514 514 return stats[3] > 0
515 515
516 516 # naming conflict in clone()
517 517 _update = update
518 518
519 519 def clean(repo, node, show_stats=True):
520 520 """forcibly switch the working directory to node, clobbering changes"""
521 521 stats = updaterepo(repo, node, True)
522 522 util.unlinkpath(repo.join('graftstate'), ignoremissing=True)
523 523 if show_stats:
524 524 _showstats(repo, stats)
525 525 return stats[3] > 0
526 526
527 527 def merge(repo, node, force=None, remind=True):
528 528 """Branch merge with node, resolving changes. Return true if any
529 529 unresolved conflicts."""
530 530 stats = mergemod.update(repo, node, True, force, False)
531 531 _showstats(repo, stats)
532 532 if stats[3]:
533 533 repo.ui.status(_("use 'hg resolve' to retry unresolved file merges "
534 534 "or 'hg update -C .' to abandon\n"))
535 535 elif remind:
536 536 repo.ui.status(_("(branch merge, don't forget to commit)\n"))
537 537 return stats[3] > 0
538 538
539 539 def _incoming(displaychlist, subreporecurse, ui, repo, source,
540 540 opts, buffered=False):
541 541 """
542 542 Helper for incoming / gincoming.
543 543 displaychlist gets called with
544 544 (remoterepo, incomingchangesetlist, displayer) parameters,
545 545 and is supposed to contain only code that can't be unified.
546 546 """
547 547 source, branches = parseurl(ui.expandpath(source), opts.get('branch'))
548 548 other = peer(repo, opts, source)
549 549 ui.status(_('comparing with %s\n') % util.hidepassword(source))
550 550 revs, checkout = addbranchrevs(repo, other, branches, opts.get('rev'))
551 551
552 552 if revs:
553 553 revs = [other.lookup(rev) for rev in revs]
554 554 other, chlist, cleanupfn = bundlerepo.getremotechanges(ui, repo, other,
555 555 revs, opts["bundle"], opts["force"])
556 556 try:
557 557 if not chlist:
558 558 ui.status(_("no changes found\n"))
559 559 return subreporecurse()
560 560
561 561 displayer = cmdutil.show_changeset(ui, other, opts, buffered)
562 562 displaychlist(other, chlist, displayer)
563 563 displayer.close()
564 564 finally:
565 565 cleanupfn()
566 566 subreporecurse()
567 567 return 0 # exit code is zero since we found incoming changes
568 568
569 569 def incoming(ui, repo, source, opts):
570 570 def subreporecurse():
571 571 ret = 1
572 572 if opts.get('subrepos'):
573 573 ctx = repo[None]
574 574 for subpath in sorted(ctx.substate):
575 575 sub = ctx.sub(subpath)
576 576 ret = min(ret, sub.incoming(ui, source, opts))
577 577 return ret
578 578
579 579 def display(other, chlist, displayer):
580 580 limit = cmdutil.loglimit(opts)
581 581 if opts.get('newest_first'):
582 582 chlist.reverse()
583 583 count = 0
584 584 for n in chlist:
585 585 if limit is not None and count >= limit:
586 586 break
587 587 parents = [p for p in other.changelog.parents(n) if p != nullid]
588 588 if opts.get('no_merges') and len(parents) == 2:
589 589 continue
590 590 count += 1
591 591 displayer.show(other[n])
592 592 return _incoming(display, subreporecurse, ui, repo, source, opts)
593 593
594 594 def _outgoing(ui, repo, dest, opts):
595 595 dest = ui.expandpath(dest or 'default-push', dest or 'default')
596 596 dest, branches = parseurl(dest, opts.get('branch'))
597 597 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
598 598 revs, checkout = addbranchrevs(repo, repo, branches, opts.get('rev'))
599 599 if revs:
600 600 revs = [repo.lookup(rev) for rev in scmutil.revrange(repo, revs)]
601 601
602 602 other = peer(repo, opts, dest)
603 603 outgoing = discovery.findcommonoutgoing(repo.unfiltered(), other, revs,
604 604 force=opts.get('force'))
605 605 o = outgoing.missing
606 606 if not o:
607 607 scmutil.nochangesfound(repo.ui, repo, outgoing.excluded)
608 608 return o, other
609 609
610 610 def outgoing(ui, repo, dest, opts):
611 611 def recurse():
612 612 ret = 1
613 613 if opts.get('subrepos'):
614 614 ctx = repo[None]
615 615 for subpath in sorted(ctx.substate):
616 616 sub = ctx.sub(subpath)
617 617 ret = min(ret, sub.outgoing(ui, dest, opts))
618 618 return ret
619 619
620 620 limit = cmdutil.loglimit(opts)
621 621 o, other = _outgoing(ui, repo, dest, opts)
622 622 if not o:
623 623 cmdutil.outgoinghooks(ui, repo, other, opts, o)
624 624 return recurse()
625 625
626 626 if opts.get('newest_first'):
627 627 o.reverse()
628 628 displayer = cmdutil.show_changeset(ui, repo, opts)
629 629 count = 0
630 630 for n in o:
631 631 if limit is not None and count >= limit:
632 632 break
633 633 parents = [p for p in repo.changelog.parents(n) if p != nullid]
634 634 if opts.get('no_merges') and len(parents) == 2:
635 635 continue
636 636 count += 1
637 637 displayer.show(repo[n])
638 638 displayer.close()
639 639 cmdutil.outgoinghooks(ui, repo, other, opts, o)
640 640 recurse()
641 641 return 0 # exit code is zero since we found outgoing changes
642 642
643 643 def revert(repo, node, choose):
644 644 """revert changes to revision in node without updating dirstate"""
645 645 return mergemod.update(repo, node, False, True, choose)[3] > 0
646 646
647 647 def verify(repo):
648 648 """verify the consistency of a repository"""
649 649 return verifymod.verify(repo)
650 650
651 651 def remoteui(src, opts):
652 652 'build a remote ui from ui or repo and opts'
653 653 if util.safehasattr(src, 'baseui'): # looks like a repository
654 654 dst = src.baseui.copy() # drop repo-specific config
655 655 src = src.ui # copy target options from repo
656 656 else: # assume it's a global ui object
657 657 dst = src.copy() # keep all global options
658 658
659 659 # copy ssh-specific options
660 660 for o in 'ssh', 'remotecmd':
661 661 v = opts.get(o) or src.config('ui', o)
662 662 if v:
663 663 dst.setconfig("ui", o, v, 'copied')
664 664
665 665 # copy bundle-specific options
666 666 r = src.config('bundle', 'mainreporoot')
667 667 if r:
668 668 dst.setconfig('bundle', 'mainreporoot', r, 'copied')
669 669
670 670 # copy selected local settings to the remote ui
671 671 for sect in ('auth', 'hostfingerprints', 'http_proxy'):
672 672 for key, val in src.configitems(sect):
673 673 dst.setconfig(sect, key, val, 'copied')
674 674 v = src.config('web', 'cacerts')
675 if v:
675 if v == '!':
676 dst.setconfig('web', 'cacerts', v, 'copied')
677 elif v:
676 678 dst.setconfig('web', 'cacerts', util.expandpath(v), 'copied')
677 679
678 680 return dst
@@ -1,221 +1,222
1 1 # sslutil.py - SSL handling for mercurial
2 2 #
3 3 # Copyright 2005, 2006, 2007, 2008 Matt Mackall <mpm@selenic.com>
4 4 # Copyright 2006, 2007 Alexis S. L. Carvalho <alexis@cecm.usp.br>
5 5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
6 6 #
7 7 # This software may be used and distributed according to the terms of the
8 8 # GNU General Public License version 2 or any later version.
9 9 import os, sys
10 10
11 11 from mercurial import util
12 12 from mercurial.i18n import _
13 13 try:
14 14 # avoid using deprecated/broken FakeSocket in python 2.6
15 15 import ssl
16 16 CERT_REQUIRED = ssl.CERT_REQUIRED
17 17 try:
18 18 ssl_context = ssl.SSLContext
19 19
20 20 def ssl_wrap_socket(sock, keyfile, certfile, cert_reqs=ssl.CERT_NONE,
21 21 ca_certs=None, serverhostname=None):
22 22 # Allow any version of SSL starting with TLSv1 and
23 23 # up. Note that specifying TLSv1 here prohibits use of
24 24 # newer standards (like TLSv1_2), so this is the right way
25 25 # to do this. Note that in the future it'd be better to
26 26 # support using ssl.create_default_context(), which sets
27 27 # up a bunch of things in smart ways (strong ciphers,
28 28 # protocol versions, etc) and is upgraded by Python
29 29 # maintainers for us, but that breaks too many things to
30 30 # do it in a hurry.
31 31 sslcontext = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
32 32 sslcontext.options &= ssl.OP_NO_SSLv2 & ssl.OP_NO_SSLv3
33 33 if certfile is not None:
34 34 sslcontext.load_cert_chain(certfile, keyfile)
35 35 sslcontext.verify_mode = cert_reqs
36 36 if ca_certs is not None:
37 37 sslcontext.load_verify_locations(cafile=ca_certs)
38 38
39 39 sslsocket = sslcontext.wrap_socket(sock,
40 40 server_hostname=serverhostname)
41 41 # check if wrap_socket failed silently because socket had been
42 42 # closed
43 43 # - see http://bugs.python.org/issue13721
44 44 if not sslsocket.cipher():
45 45 raise util.Abort(_('ssl connection failed'))
46 46 return sslsocket
47 47 except AttributeError:
48 48 def ssl_wrap_socket(sock, keyfile, certfile, cert_reqs=ssl.CERT_NONE,
49 49 ca_certs=None, serverhostname=None):
50 50 sslsocket = ssl.wrap_socket(sock, keyfile, certfile,
51 51 cert_reqs=cert_reqs, ca_certs=ca_certs,
52 52 ssl_version=ssl.PROTOCOL_TLSv1)
53 53 # check if wrap_socket failed silently because socket had been
54 54 # closed
55 55 # - see http://bugs.python.org/issue13721
56 56 if not sslsocket.cipher():
57 57 raise util.Abort(_('ssl connection failed'))
58 58 return sslsocket
59 59 except ImportError:
60 60 CERT_REQUIRED = 2
61 61
62 62 import socket, httplib
63 63
64 64 def ssl_wrap_socket(sock, keyfile, certfile, cert_reqs=CERT_REQUIRED,
65 65 ca_certs=None, serverhostname=None):
66 66 if not util.safehasattr(socket, 'ssl'):
67 67 raise util.Abort(_('Python SSL support not found'))
68 68 if ca_certs:
69 69 raise util.Abort(_(
70 70 'certificate checking requires Python 2.6'))
71 71
72 72 ssl = socket.ssl(sock, keyfile, certfile)
73 73 return httplib.FakeSocket(sock, ssl)
74 74
75 75 def _verifycert(cert, hostname):
76 76 '''Verify that cert (in socket.getpeercert() format) matches hostname.
77 77 CRLs is not handled.
78 78
79 79 Returns error message if any problems are found and None on success.
80 80 '''
81 81 if not cert:
82 82 return _('no certificate received')
83 83 dnsname = hostname.lower()
84 84 def matchdnsname(certname):
85 85 return (certname == dnsname or
86 86 '.' in dnsname and certname == '*.' + dnsname.split('.', 1)[1])
87 87
88 88 san = cert.get('subjectAltName', [])
89 89 if san:
90 90 certnames = [value.lower() for key, value in san if key == 'DNS']
91 91 for name in certnames:
92 92 if matchdnsname(name):
93 93 return None
94 94 if certnames:
95 95 return _('certificate is for %s') % ', '.join(certnames)
96 96
97 97 # subject is only checked when subjectAltName is empty
98 98 for s in cert.get('subject', []):
99 99 key, value = s[0]
100 100 if key == 'commonName':
101 101 try:
102 102 # 'subject' entries are unicode
103 103 certname = value.lower().encode('ascii')
104 104 except UnicodeEncodeError:
105 105 return _('IDN in certificate not supported')
106 106 if matchdnsname(certname):
107 107 return None
108 108 return _('certificate is for %s') % certname
109 109 return _('no commonName or subjectAltName found in certificate')
110 110
111 111
112 112 # CERT_REQUIRED means fetch the cert from the server all the time AND
113 113 # validate it against the CA store provided in web.cacerts.
114 114 #
115 115 # We COMPLETELY ignore CERT_REQUIRED on Python <= 2.5, as it's totally
116 116 # busted on those versions.
117 117
118 118 def _plainapplepython():
119 119 """return true if this seems to be a pure Apple Python that
120 120 * is unfrozen and presumably has the whole mercurial module in the file
121 121 system
122 122 * presumably is an Apple Python that uses Apple OpenSSL which has patches
123 123 for using system certificate store CAs in addition to the provided
124 124 cacerts file
125 125 """
126 126 if sys.platform != 'darwin' or util.mainfrozen():
127 127 return False
128 128 exe = (sys.executable or '').lower()
129 129 return (exe.startswith('/usr/bin/python') or
130 130 exe.startswith('/system/library/frameworks/python.framework/'))
131 131
132 132 def _defaultcacerts():
133 133 if _plainapplepython():
134 134 dummycert = os.path.join(os.path.dirname(__file__), 'dummycert.pem')
135 135 if os.path.exists(dummycert):
136 136 return dummycert
137 return None
137 return '!'
138 138
139 139 def sslkwargs(ui, host):
140 140 kws = {}
141 141 hostfingerprint = ui.config('hostfingerprints', host)
142 142 if hostfingerprint:
143 143 return kws
144 144 cacerts = ui.config('web', 'cacerts')
145 if cacerts:
145 if cacerts == '!':
146 pass
147 elif cacerts:
146 148 cacerts = util.expandpath(cacerts)
147 149 if not os.path.exists(cacerts):
148 150 raise util.Abort(_('could not find web.cacerts: %s') % cacerts)
149 elif cacerts is None:
150 dummycert = _defaultcacerts()
151 if dummycert:
152 ui.debug('using %s to enable OS X system CA\n' % dummycert)
153 ui.setconfig('web', 'cacerts', dummycert, 'dummy')
154 cacerts = dummycert
155 if cacerts:
151 else:
152 cacerts = _defaultcacerts()
153 if cacerts and cacerts != '!':
154 ui.debug('using %s to enable OS X system CA\n' % cacerts)
155 ui.setconfig('web', 'cacerts', cacerts, 'defaultcacerts')
156 if cacerts != '!':
156 157 kws.update({'ca_certs': cacerts,
157 158 'cert_reqs': CERT_REQUIRED,
158 159 })
159 160 return kws
160 161
161 162 class validator(object):
162 163 def __init__(self, ui, host):
163 164 self.ui = ui
164 165 self.host = host
165 166
166 167 def __call__(self, sock, strict=False):
167 168 host = self.host
168 169 cacerts = self.ui.config('web', 'cacerts')
169 170 hostfingerprint = self.ui.config('hostfingerprints', host)
170 171 if not getattr(sock, 'getpeercert', False): # python 2.5 ?
171 172 if hostfingerprint:
172 173 raise util.Abort(_("host fingerprint for %s can't be "
173 174 "verified (Python too old)") % host)
174 175 if strict:
175 176 raise util.Abort(_("certificate for %s can't be verified "
176 177 "(Python too old)") % host)
177 178 if self.ui.configbool('ui', 'reportoldssl', True):
178 179 self.ui.warn(_("warning: certificate for %s can't be verified "
179 180 "(Python too old)\n") % host)
180 181 return
181 182
182 183 if not sock.cipher(): # work around http://bugs.python.org/issue13721
183 184 raise util.Abort(_('%s ssl connection error') % host)
184 185 try:
185 186 peercert = sock.getpeercert(True)
186 187 peercert2 = sock.getpeercert()
187 188 except AttributeError:
188 189 raise util.Abort(_('%s ssl connection error') % host)
189 190
190 191 if not peercert:
191 192 raise util.Abort(_('%s certificate error: '
192 193 'no certificate received') % host)
193 194 peerfingerprint = util.sha1(peercert).hexdigest()
194 195 nicefingerprint = ":".join([peerfingerprint[x:x + 2]
195 196 for x in xrange(0, len(peerfingerprint), 2)])
196 197 if hostfingerprint:
197 198 if peerfingerprint.lower() != \
198 199 hostfingerprint.replace(':', '').lower():
199 200 raise util.Abort(_('certificate for %s has unexpected '
200 201 'fingerprint %s') % (host, nicefingerprint),
201 202 hint=_('check hostfingerprint configuration'))
202 203 self.ui.debug('%s certificate matched fingerprint %s\n' %
203 204 (host, nicefingerprint))
204 elif cacerts:
205 elif cacerts != '!':
205 206 msg = _verifycert(peercert2, host)
206 207 if msg:
207 208 raise util.Abort(_('%s certificate error: %s') % (host, msg),
208 209 hint=_('configure hostfingerprint %s or use '
209 210 '--insecure to connect insecurely') %
210 211 nicefingerprint)
211 212 self.ui.debug('%s certificate successfully verified\n' % host)
212 213 elif strict:
213 214 raise util.Abort(_('%s certificate with fingerprint %s not '
214 215 'verified') % (host, nicefingerprint),
215 216 hint=_('check hostfingerprints or web.cacerts '
216 217 'config setting'))
217 218 else:
218 219 self.ui.warn(_('warning: %s certificate with fingerprint %s not '
219 220 'verified (check hostfingerprints or web.cacerts '
220 221 'config setting)\n') %
221 222 (host, nicefingerprint))
@@ -1,374 +1,374
1 1 import os, stat
2 2 import re
3 3 import socket
4 4 import sys
5 5 import tempfile
6 6
7 7 tempprefix = 'hg-hghave-'
8 8
9 9 checks = {
10 10 "true": (lambda: True, "yak shaving"),
11 11 "false": (lambda: False, "nail clipper"),
12 12 }
13 13
14 14 def check(name, desc):
15 15 def decorator(func):
16 16 checks[name] = (func, desc)
17 17 return func
18 18 return decorator
19 19
20 20 def matchoutput(cmd, regexp, ignorestatus=False):
21 21 """Return True if cmd executes successfully and its output
22 22 is matched by the supplied regular expression.
23 23 """
24 24 r = re.compile(regexp)
25 25 fh = os.popen(cmd)
26 26 s = fh.read()
27 27 try:
28 28 ret = fh.close()
29 29 except IOError:
30 30 # Happen in Windows test environment
31 31 ret = 1
32 32 return (ignorestatus or ret is None) and r.search(s)
33 33
34 34 @check("baz", "GNU Arch baz client")
35 35 def has_baz():
36 36 return matchoutput('baz --version 2>&1', r'baz Bazaar version')
37 37
38 38 @check("bzr", "Canonical's Bazaar client")
39 39 def has_bzr():
40 40 try:
41 41 import bzrlib
42 42 return bzrlib.__doc__ is not None
43 43 except ImportError:
44 44 return False
45 45
46 46 @check("bzr114", "Canonical's Bazaar client >= 1.14")
47 47 def has_bzr114():
48 48 try:
49 49 import bzrlib
50 50 return (bzrlib.__doc__ is not None
51 51 and bzrlib.version_info[:2] >= (1, 14))
52 52 except ImportError:
53 53 return False
54 54
55 55 @check("cvs", "cvs client/server")
56 56 def has_cvs():
57 57 re = r'Concurrent Versions System.*?server'
58 58 return matchoutput('cvs --version 2>&1', re) and not has_msys()
59 59
60 60 @check("cvs112", "cvs client/server >= 1.12")
61 61 def has_cvs112():
62 62 re = r'Concurrent Versions System \(CVS\) 1.12.*?server'
63 63 return matchoutput('cvs --version 2>&1', re) and not has_msys()
64 64
65 65 @check("darcs", "darcs client")
66 66 def has_darcs():
67 67 return matchoutput('darcs --version', r'2\.[2-9]', True)
68 68
69 69 @check("mtn", "monotone client (>= 1.0)")
70 70 def has_mtn():
71 71 return matchoutput('mtn --version', r'monotone', True) and not matchoutput(
72 72 'mtn --version', r'monotone 0\.', True)
73 73
74 74 @check("eol-in-paths", "end-of-lines in paths")
75 75 def has_eol_in_paths():
76 76 try:
77 77 fd, path = tempfile.mkstemp(dir='.', prefix=tempprefix, suffix='\n\r')
78 78 os.close(fd)
79 79 os.remove(path)
80 80 return True
81 81 except (IOError, OSError):
82 82 return False
83 83
84 84 @check("execbit", "executable bit")
85 85 def has_executablebit():
86 86 try:
87 87 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
88 88 fh, fn = tempfile.mkstemp(dir='.', prefix=tempprefix)
89 89 try:
90 90 os.close(fh)
91 91 m = os.stat(fn).st_mode & 0777
92 92 new_file_has_exec = m & EXECFLAGS
93 93 os.chmod(fn, m ^ EXECFLAGS)
94 94 exec_flags_cannot_flip = ((os.stat(fn).st_mode & 0777) == m)
95 95 finally:
96 96 os.unlink(fn)
97 97 except (IOError, OSError):
98 98 # we don't care, the user probably won't be able to commit anyway
99 99 return False
100 100 return not (new_file_has_exec or exec_flags_cannot_flip)
101 101
102 102 @check("icasefs", "case insensitive file system")
103 103 def has_icasefs():
104 104 # Stolen from mercurial.util
105 105 fd, path = tempfile.mkstemp(dir='.', prefix=tempprefix)
106 106 os.close(fd)
107 107 try:
108 108 s1 = os.stat(path)
109 109 d, b = os.path.split(path)
110 110 p2 = os.path.join(d, b.upper())
111 111 if path == p2:
112 112 p2 = os.path.join(d, b.lower())
113 113 try:
114 114 s2 = os.stat(p2)
115 115 return s2 == s1
116 116 except OSError:
117 117 return False
118 118 finally:
119 119 os.remove(path)
120 120
121 121 @check("fifo", "named pipes")
122 122 def has_fifo():
123 123 if getattr(os, "mkfifo", None) is None:
124 124 return False
125 125 name = tempfile.mktemp(dir='.', prefix=tempprefix)
126 126 try:
127 127 os.mkfifo(name)
128 128 os.unlink(name)
129 129 return True
130 130 except OSError:
131 131 return False
132 132
133 133 @check("killdaemons", 'killdaemons.py support')
134 134 def has_killdaemons():
135 135 return True
136 136
137 137 @check("cacheable", "cacheable filesystem")
138 138 def has_cacheable_fs():
139 139 from mercurial import util
140 140
141 141 fd, path = tempfile.mkstemp(dir='.', prefix=tempprefix)
142 142 os.close(fd)
143 143 try:
144 144 return util.cachestat(path).cacheable()
145 145 finally:
146 146 os.remove(path)
147 147
148 148 @check("lsprof", "python lsprof module")
149 149 def has_lsprof():
150 150 try:
151 151 import _lsprof
152 152 _lsprof.Profiler # silence unused import warning
153 153 return True
154 154 except ImportError:
155 155 return False
156 156
157 157 @check("gettext", "GNU Gettext (msgfmt)")
158 158 def has_gettext():
159 159 return matchoutput('msgfmt --version', 'GNU gettext-tools')
160 160
161 161 @check("git", "git command line client")
162 162 def has_git():
163 163 return matchoutput('git --version 2>&1', r'^git version')
164 164
165 165 @check("docutils", "Docutils text processing library")
166 166 def has_docutils():
167 167 try:
168 168 from docutils.core import publish_cmdline
169 169 publish_cmdline # silence unused import
170 170 return True
171 171 except ImportError:
172 172 return False
173 173
174 174 def getsvnversion():
175 175 m = matchoutput('svn --version --quiet 2>&1', r'^(\d+)\.(\d+)')
176 176 if not m:
177 177 return (0, 0)
178 178 return (int(m.group(1)), int(m.group(2)))
179 179
180 180 @check("svn15", "subversion client and admin tools >= 1.5")
181 181 def has_svn15():
182 182 return getsvnversion() >= (1, 5)
183 183
184 184 @check("svn13", "subversion client and admin tools >= 1.3")
185 185 def has_svn13():
186 186 return getsvnversion() >= (1, 3)
187 187
188 188 @check("svn", "subversion client and admin tools")
189 189 def has_svn():
190 190 return matchoutput('svn --version 2>&1', r'^svn, version') and \
191 191 matchoutput('svnadmin --version 2>&1', r'^svnadmin, version')
192 192
193 193 @check("svn-bindings", "subversion python bindings")
194 194 def has_svn_bindings():
195 195 try:
196 196 import svn.core
197 197 version = svn.core.SVN_VER_MAJOR, svn.core.SVN_VER_MINOR
198 198 if version < (1, 4):
199 199 return False
200 200 return True
201 201 except ImportError:
202 202 return False
203 203
204 204 @check("p4", "Perforce server and client")
205 205 def has_p4():
206 206 return (matchoutput('p4 -V', r'Rev\. P4/') and
207 207 matchoutput('p4d -V', r'Rev\. P4D/'))
208 208
209 209 @check("symlink", "symbolic links")
210 210 def has_symlink():
211 211 if getattr(os, "symlink", None) is None:
212 212 return False
213 213 name = tempfile.mktemp(dir='.', prefix=tempprefix)
214 214 try:
215 215 os.symlink(".", name)
216 216 os.unlink(name)
217 217 return True
218 218 except (OSError, AttributeError):
219 219 return False
220 220
221 221 @check("hardlink", "hardlinks")
222 222 def has_hardlink():
223 223 from mercurial import util
224 224 fh, fn = tempfile.mkstemp(dir='.', prefix=tempprefix)
225 225 os.close(fh)
226 226 name = tempfile.mktemp(dir='.', prefix=tempprefix)
227 227 try:
228 228 try:
229 229 util.oslink(fn, name)
230 230 os.unlink(name)
231 231 return True
232 232 except OSError:
233 233 return False
234 234 finally:
235 235 os.unlink(fn)
236 236
237 237 @check("tla", "GNU Arch tla client")
238 238 def has_tla():
239 239 return matchoutput('tla --version 2>&1', r'The GNU Arch Revision')
240 240
241 241 @check("gpg", "gpg client")
242 242 def has_gpg():
243 243 return matchoutput('gpg --version 2>&1', r'GnuPG')
244 244
245 245 @check("unix-permissions", "unix-style permissions")
246 246 def has_unix_permissions():
247 247 d = tempfile.mkdtemp(dir='.', prefix=tempprefix)
248 248 try:
249 249 fname = os.path.join(d, 'foo')
250 250 for umask in (077, 007, 022):
251 251 os.umask(umask)
252 252 f = open(fname, 'w')
253 253 f.close()
254 254 mode = os.stat(fname).st_mode
255 255 os.unlink(fname)
256 256 if mode & 0777 != ~umask & 0666:
257 257 return False
258 258 return True
259 259 finally:
260 260 os.rmdir(d)
261 261
262 262 @check("unix-socket", "AF_UNIX socket family")
263 263 def has_unix_socket():
264 264 return getattr(socket, 'AF_UNIX', None) is not None
265 265
266 266 @check("root", "root permissions")
267 267 def has_root():
268 268 return getattr(os, 'geteuid', None) and os.geteuid() == 0
269 269
270 270 @check("pyflakes", "Pyflakes python linter")
271 271 def has_pyflakes():
272 272 return matchoutput("sh -c \"echo 'import re' 2>&1 | pyflakes\"",
273 273 r"<stdin>:1: 're' imported but unused",
274 274 True)
275 275
276 276 @check("pygments", "Pygments source highlighting library")
277 277 def has_pygments():
278 278 try:
279 279 import pygments
280 280 pygments.highlight # silence unused import warning
281 281 return True
282 282 except ImportError:
283 283 return False
284 284
285 285 @check("python243", "python >= 2.4.3")
286 286 def has_python243():
287 287 return sys.version_info >= (2, 4, 3)
288 288
289 289 @check("json", "some json module available")
290 290 def has_json():
291 291 try:
292 292 import json
293 293 json.dumps
294 294 return True
295 295 except ImportError:
296 296 try:
297 297 import simplejson as json
298 298 json.dumps
299 299 return True
300 300 except ImportError:
301 301 pass
302 302 return False
303 303
304 304 @check("outer-repo", "outer repo")
305 305 def has_outer_repo():
306 306 # failing for other reasons than 'no repo' imply that there is a repo
307 307 return not matchoutput('hg root 2>&1',
308 308 r'abort: no repository found', True)
309 309
310 310 @check("ssl", ("(python >= 2.6 ssl module and python OpenSSL) "
311 311 "OR python >= 2.7.9 ssl"))
312 312 def has_ssl():
313 313 try:
314 314 import ssl
315 315 if getattr(ssl, 'create_default_context', False):
316 316 return True
317 317 import OpenSSL
318 318 OpenSSL.SSL.Context
319 319 return True
320 320 except ImportError:
321 321 return False
322 322
323 323 @check("defaultcacerts", "can verify SSL certs by system's CA certs store")
324 324 def has_defaultcacerts():
325 325 from mercurial import sslutil
326 return sslutil._defaultcacerts()
326 return sslutil._defaultcacerts() != '!'
327 327
328 328 @check("windows", "Windows")
329 329 def has_windows():
330 330 return os.name == 'nt'
331 331
332 332 @check("system-sh", "system() uses sh")
333 333 def has_system_sh():
334 334 return os.name != 'nt'
335 335
336 336 @check("serve", "platform and python can manage 'hg serve -d'")
337 337 def has_serve():
338 338 return os.name != 'nt' # gross approximation
339 339
340 340 @check("test-repo", "running tests from repository")
341 341 def has_test_repo():
342 342 t = os.environ["TESTDIR"]
343 343 return os.path.isdir(os.path.join(t, "..", ".hg"))
344 344
345 345 @check("tic", "terminfo compiler and curses module")
346 346 def has_tic():
347 347 try:
348 348 import curses
349 349 curses.COLOR_BLUE
350 350 return matchoutput('test -x "`which tic`"', '')
351 351 except ImportError:
352 352 return False
353 353
354 354 @check("msys", "Windows with MSYS")
355 355 def has_msys():
356 356 return os.getenv('MSYSTEM')
357 357
358 358 @check("aix", "AIX")
359 359 def has_aix():
360 360 return sys.platform.startswith("aix")
361 361
362 362 @check("osx", "OS X")
363 363 def has_osx():
364 364 return sys.platform == 'darwin'
365 365
366 366 @check("absimport", "absolute_import in __future__")
367 367 def has_absimport():
368 368 import __future__
369 369 from mercurial import util
370 370 return util.safehasattr(__future__, "absolute_import")
371 371
372 372 @check("py3k", "running with Python 3.x")
373 373 def has_py3k():
374 374 return 3 == sys.version_info[0]
@@ -1,299 +1,299
1 1 #require serve ssl
2 2
3 3 Proper https client requires the built-in ssl from Python 2.6.
4 4
5 5 Certificates created with:
6 6 printf '.\n.\n.\n.\n.\nlocalhost\nhg@localhost\n' | \
7 7 openssl req -newkey rsa:512 -keyout priv.pem -nodes -x509 -days 9000 -out pub.pem
8 8 Can be dumped with:
9 9 openssl x509 -in pub.pem -text
10 10
11 11 $ cat << EOT > priv.pem
12 12 > -----BEGIN PRIVATE KEY-----
13 13 > MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEApjCWeYGrIa/Vo7LH
14 14 > aRF8ou0tbgHKE33Use/whCnKEUm34rDaXQd4lxxX6aDWg06n9tiVStAKTgQAHJY8
15 15 > j/xgSwIDAQABAkBxHC6+Qlf0VJXGlb6NL16yEVVTQxqDS6hA9zqu6TZjrr0YMfzc
16 16 > EGNIiZGt7HCBL0zO+cPDg/LeCZc6HQhf0KrhAiEAzlJq4hWWzvguWFIJWSoBeBUG
17 17 > MF1ACazQO7PYE8M0qfECIQDONHHP0SKZzz/ZwBZcAveC5K61f/v9hONFwbeYulzR
18 18 > +wIgc9SvbtgB/5Yzpp//4ZAEnR7oh5SClCvyB+KSx52K3nECICbhQphhoXmI10wy
19 19 > aMTellaq0bpNMHFDziqH9RsqAHhjAiEAgYGxfzkftt5IUUn/iFK89aaIpyrpuaAh
20 20 > HY8gUVkVRVs=
21 21 > -----END PRIVATE KEY-----
22 22 > EOT
23 23
24 24 $ cat << EOT > pub.pem
25 25 > -----BEGIN CERTIFICATE-----
26 26 > MIIBqzCCAVWgAwIBAgIJANAXFFyWjGnRMA0GCSqGSIb3DQEBBQUAMDExEjAQBgNV
27 27 > BAMMCWxvY2FsaG9zdDEbMBkGCSqGSIb3DQEJARYMaGdAbG9jYWxob3N0MB4XDTEw
28 28 > MTAxNDIwMzAxNFoXDTM1MDYwNTIwMzAxNFowMTESMBAGA1UEAwwJbG9jYWxob3N0
29 29 > MRswGQYJKoZIhvcNAQkBFgxoZ0Bsb2NhbGhvc3QwXDANBgkqhkiG9w0BAQEFAANL
30 30 > ADBIAkEApjCWeYGrIa/Vo7LHaRF8ou0tbgHKE33Use/whCnKEUm34rDaXQd4lxxX
31 31 > 6aDWg06n9tiVStAKTgQAHJY8j/xgSwIDAQABo1AwTjAdBgNVHQ4EFgQUE6sA+amm
32 32 > r24dGX0kpjxOgO45hzQwHwYDVR0jBBgwFoAUE6sA+ammr24dGX0kpjxOgO45hzQw
33 33 > DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAANBAFArvQFiAZJgQczRsbYlG1xl
34 34 > t+truk37w5B3m3Ick1ntRcQrqs+hf0CO1q6Squ144geYaQ8CDirSR92fICELI1c=
35 35 > -----END CERTIFICATE-----
36 36 > EOT
37 37 $ cat priv.pem pub.pem >> server.pem
38 38 $ PRIV=`pwd`/server.pem
39 39
40 40 $ cat << EOT > pub-other.pem
41 41 > -----BEGIN CERTIFICATE-----
42 42 > MIIBqzCCAVWgAwIBAgIJALwZS731c/ORMA0GCSqGSIb3DQEBBQUAMDExEjAQBgNV
43 43 > BAMMCWxvY2FsaG9zdDEbMBkGCSqGSIb3DQEJARYMaGdAbG9jYWxob3N0MB4XDTEw
44 44 > MTAxNDIwNDUxNloXDTM1MDYwNTIwNDUxNlowMTESMBAGA1UEAwwJbG9jYWxob3N0
45 45 > MRswGQYJKoZIhvcNAQkBFgxoZ0Bsb2NhbGhvc3QwXDANBgkqhkiG9w0BAQEFAANL
46 46 > ADBIAkEAsxsapLbHrqqUKuQBxdpK4G3m2LjtyrTSdpzzzFlecxd5yhNP6AyWrufo
47 47 > K4VMGo2xlu9xOo88nDSUNSKPuD09MwIDAQABo1AwTjAdBgNVHQ4EFgQUoIB1iMhN
48 48 > y868rpQ2qk9dHnU6ebswHwYDVR0jBBgwFoAUoIB1iMhNy868rpQ2qk9dHnU6ebsw
49 49 > DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAANBAJ544f125CsE7J2t55PdFaF6
50 50 > bBlNBb91FCywBgSjhBjf+GG3TNPwrPdc3yqeq+hzJiuInqbOBv9abmMyq8Wsoig=
51 51 > -----END CERTIFICATE-----
52 52 > EOT
53 53
54 54 pub.pem patched with other notBefore / notAfter:
55 55
56 56 $ cat << EOT > pub-not-yet.pem
57 57 > -----BEGIN CERTIFICATE-----
58 58 > MIIBqzCCAVWgAwIBAgIJANAXFFyWjGnRMA0GCSqGSIb3DQEBBQUAMDExEjAQBgNVBAMMCWxvY2Fs
59 59 > aG9zdDEbMBkGCSqGSIb3DQEJARYMaGdAbG9jYWxob3N0MB4XDTM1MDYwNTIwMzAxNFoXDTM1MDYw
60 60 > NTIwMzAxNFowMTESMBAGA1UEAwwJbG9jYWxob3N0MRswGQYJKoZIhvcNAQkBFgxoZ0Bsb2NhbGhv
61 61 > c3QwXDANBgkqhkiG9w0BAQEFAANLADBIAkEApjCWeYGrIa/Vo7LHaRF8ou0tbgHKE33Use/whCnK
62 62 > EUm34rDaXQd4lxxX6aDWg06n9tiVStAKTgQAHJY8j/xgSwIDAQABo1AwTjAdBgNVHQ4EFgQUE6sA
63 63 > +ammr24dGX0kpjxOgO45hzQwHwYDVR0jBBgwFoAUE6sA+ammr24dGX0kpjxOgO45hzQwDAYDVR0T
64 64 > BAUwAwEB/zANBgkqhkiG9w0BAQUFAANBAJXV41gWnkgC7jcpPpFRSUSZaxyzrXmD1CIqQf0WgVDb
65 65 > /12E0vR2DuZitgzUYtBaofM81aTtc0a2/YsrmqePGm0=
66 66 > -----END CERTIFICATE-----
67 67 > EOT
68 68 $ cat priv.pem pub-not-yet.pem > server-not-yet.pem
69 69
70 70 $ cat << EOT > pub-expired.pem
71 71 > -----BEGIN CERTIFICATE-----
72 72 > MIIBqzCCAVWgAwIBAgIJANAXFFyWjGnRMA0GCSqGSIb3DQEBBQUAMDExEjAQBgNVBAMMCWxvY2Fs
73 73 > aG9zdDEbMBkGCSqGSIb3DQEJARYMaGdAbG9jYWxob3N0MB4XDTEwMTAxNDIwMzAxNFoXDTEwMTAx
74 74 > NDIwMzAxNFowMTESMBAGA1UEAwwJbG9jYWxob3N0MRswGQYJKoZIhvcNAQkBFgxoZ0Bsb2NhbGhv
75 75 > c3QwXDANBgkqhkiG9w0BAQEFAANLADBIAkEApjCWeYGrIa/Vo7LHaRF8ou0tbgHKE33Use/whCnK
76 76 > EUm34rDaXQd4lxxX6aDWg06n9tiVStAKTgQAHJY8j/xgSwIDAQABo1AwTjAdBgNVHQ4EFgQUE6sA
77 77 > +ammr24dGX0kpjxOgO45hzQwHwYDVR0jBBgwFoAUE6sA+ammr24dGX0kpjxOgO45hzQwDAYDVR0T
78 78 > BAUwAwEB/zANBgkqhkiG9w0BAQUFAANBAJfk57DTRf2nUbYaMSlVAARxMNbFGOjQhAUtY400GhKt
79 79 > 2uiKCNGKXVXD3AHWe13yHc5KttzbHQStE5Nm/DlWBWQ=
80 80 > -----END CERTIFICATE-----
81 81 > EOT
82 82 $ cat priv.pem pub-expired.pem > server-expired.pem
83 83
84 84 $ hg init test
85 85 $ cd test
86 86 $ echo foo>foo
87 87 $ mkdir foo.d foo.d/bAr.hg.d foo.d/baR.d.hg
88 88 $ echo foo>foo.d/foo
89 89 $ echo bar>foo.d/bAr.hg.d/BaR
90 90 $ echo bar>foo.d/baR.d.hg/bAR
91 91 $ hg commit -A -m 1
92 92 adding foo
93 93 adding foo.d/bAr.hg.d/BaR
94 94 adding foo.d/baR.d.hg/bAR
95 95 adding foo.d/foo
96 96 $ hg serve -p $HGPORT -d --pid-file=../hg0.pid --certificate=$PRIV
97 97 $ cat ../hg0.pid >> $DAEMON_PIDS
98 98
99 99 cacert not found
100 100
101 101 $ hg in --config web.cacerts=no-such.pem https://localhost:$HGPORT/
102 102 abort: could not find web.cacerts: no-such.pem
103 103 [255]
104 104
105 105 Test server address cannot be reused
106 106
107 107 #if windows
108 108 $ hg serve -p $HGPORT --certificate=$PRIV 2>&1
109 109 abort: cannot start server at ':$HGPORT':
110 110 [255]
111 111 #else
112 112 $ hg serve -p $HGPORT --certificate=$PRIV 2>&1
113 113 abort: cannot start server at ':$HGPORT': Address already in use
114 114 [255]
115 115 #endif
116 116 $ cd ..
117 117
118 118 OS X has a dummy CA cert that enables use of the system CA store when using
119 119 Apple's OpenSSL. This trick do not work with plain OpenSSL.
120 120
121 121 $ DISABLEOSXDUMMYCERT=
122 122 #if defaultcacerts
123 123 $ hg clone https://localhost:$HGPORT/ copy-pull
124 124 abort: error: *certificate verify failed* (glob)
125 125 [255]
126 126
127 $ DISABLEOSXDUMMYCERT="--config=web.cacerts="
127 $ DISABLEOSXDUMMYCERT="--config=web.cacerts=!"
128 128 #endif
129 129
130 130 clone via pull
131 131
132 132 $ hg clone https://localhost:$HGPORT/ copy-pull $DISABLEOSXDUMMYCERT
133 133 warning: localhost certificate with fingerprint 91:4f:1a:ff:87:24:9c:09:b6:85:9b:88:b1:90:6d:30:75:64:91:ca not verified (check hostfingerprints or web.cacerts config setting)
134 134 requesting all changes
135 135 adding changesets
136 136 adding manifests
137 137 adding file changes
138 138 added 1 changesets with 4 changes to 4 files
139 139 updating to branch default
140 140 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
141 141 $ hg verify -R copy-pull
142 142 checking changesets
143 143 checking manifests
144 144 crosschecking files in changesets and manifests
145 145 checking files
146 146 4 files, 1 changesets, 4 total revisions
147 147 $ cd test
148 148 $ echo bar > bar
149 149 $ hg commit -A -d '1 0' -m 2
150 150 adding bar
151 151 $ cd ..
152 152
153 153 pull without cacert
154 154
155 155 $ cd copy-pull
156 156 $ echo '[hooks]' >> .hg/hgrc
157 157 $ echo "changegroup = python \"$TESTDIR/printenv.py\" changegroup" >> .hg/hgrc
158 158 $ hg pull $DISABLEOSXDUMMYCERT
159 159 pulling from https://localhost:$HGPORT/
160 160 warning: localhost certificate with fingerprint 91:4f:1a:ff:87:24:9c:09:b6:85:9b:88:b1:90:6d:30:75:64:91:ca not verified (check hostfingerprints or web.cacerts config setting)
161 161 searching for changes
162 162 adding changesets
163 163 adding manifests
164 164 adding file changes
165 165 added 1 changesets with 1 changes to 1 files
166 166 changegroup hook: HG_NODE=5fed3813f7f5e1824344fdc9cf8f63bb662c292d HG_SOURCE=pull HG_URL=https://localhost:$HGPORT/
167 167 (run 'hg update' to get a working copy)
168 168 $ cd ..
169 169
170 170 cacert configured in local repo
171 171
172 172 $ cp copy-pull/.hg/hgrc copy-pull/.hg/hgrc.bu
173 173 $ echo "[web]" >> copy-pull/.hg/hgrc
174 174 $ echo "cacerts=`pwd`/pub.pem" >> copy-pull/.hg/hgrc
175 175 $ hg -R copy-pull pull --traceback
176 176 pulling from https://localhost:$HGPORT/
177 177 searching for changes
178 178 no changes found
179 179 $ mv copy-pull/.hg/hgrc.bu copy-pull/.hg/hgrc
180 180
181 181 cacert configured globally, also testing expansion of environment
182 182 variables in the filename
183 183
184 184 $ echo "[web]" >> $HGRCPATH
185 185 $ echo 'cacerts=$P/pub.pem' >> $HGRCPATH
186 186 $ P=`pwd` hg -R copy-pull pull
187 187 pulling from https://localhost:$HGPORT/
188 188 searching for changes
189 189 no changes found
190 190 $ P=`pwd` hg -R copy-pull pull --insecure
191 191 pulling from https://localhost:$HGPORT/
192 192 warning: localhost certificate with fingerprint 91:4f:1a:ff:87:24:9c:09:b6:85:9b:88:b1:90:6d:30:75:64:91:ca not verified (check hostfingerprints or web.cacerts config setting)
193 193 searching for changes
194 194 no changes found
195 195
196 196 cacert mismatch
197 197
198 198 $ hg -R copy-pull pull --config web.cacerts=pub.pem https://127.0.0.1:$HGPORT/
199 199 pulling from https://127.0.0.1:$HGPORT/
200 200 abort: 127.0.0.1 certificate error: certificate is for localhost
201 201 (configure hostfingerprint 91:4f:1a:ff:87:24:9c:09:b6:85:9b:88:b1:90:6d:30:75:64:91:ca or use --insecure to connect insecurely)
202 202 [255]
203 203 $ hg -R copy-pull pull --config web.cacerts=pub.pem https://127.0.0.1:$HGPORT/ --insecure
204 204 pulling from https://127.0.0.1:$HGPORT/
205 205 warning: 127.0.0.1 certificate with fingerprint 91:4f:1a:ff:87:24:9c:09:b6:85:9b:88:b1:90:6d:30:75:64:91:ca not verified (check hostfingerprints or web.cacerts config setting)
206 206 searching for changes
207 207 no changes found
208 208 $ hg -R copy-pull pull --config web.cacerts=pub-other.pem
209 209 pulling from https://localhost:$HGPORT/
210 210 abort: error: *certificate verify failed* (glob)
211 211 [255]
212 212 $ hg -R copy-pull pull --config web.cacerts=pub-other.pem --insecure
213 213 pulling from https://localhost:$HGPORT/
214 214 warning: localhost certificate with fingerprint 91:4f:1a:ff:87:24:9c:09:b6:85:9b:88:b1:90:6d:30:75:64:91:ca not verified (check hostfingerprints or web.cacerts config setting)
215 215 searching for changes
216 216 no changes found
217 217
218 218 Test server cert which isn't valid yet
219 219
220 220 $ hg -R test serve -p $HGPORT1 -d --pid-file=hg1.pid --certificate=server-not-yet.pem
221 221 $ cat hg1.pid >> $DAEMON_PIDS
222 222 $ hg -R copy-pull pull --config web.cacerts=pub-not-yet.pem https://localhost:$HGPORT1/
223 223 pulling from https://localhost:$HGPORT1/
224 224 abort: error: *certificate verify failed* (glob)
225 225 [255]
226 226
227 227 Test server cert which no longer is valid
228 228
229 229 $ hg -R test serve -p $HGPORT2 -d --pid-file=hg2.pid --certificate=server-expired.pem
230 230 $ cat hg2.pid >> $DAEMON_PIDS
231 231 $ hg -R copy-pull pull --config web.cacerts=pub-expired.pem https://localhost:$HGPORT2/
232 232 pulling from https://localhost:$HGPORT2/
233 233 abort: error: *certificate verify failed* (glob)
234 234 [255]
235 235
236 236 Fingerprints
237 237
238 238 $ echo "[hostfingerprints]" >> copy-pull/.hg/hgrc
239 239 $ echo "localhost = 91:4f:1a:ff:87:24:9c:09:b6:85:9b:88:b1:90:6d:30:75:64:91:ca" >> copy-pull/.hg/hgrc
240 240 $ echo "127.0.0.1 = 914f1aff87249c09b6859b88b1906d30756491ca" >> copy-pull/.hg/hgrc
241 241
242 242 - works without cacerts
243 $ hg -R copy-pull id https://localhost:$HGPORT/ --config web.cacerts=
243 $ hg -R copy-pull id https://localhost:$HGPORT/ --config web.cacerts=!
244 244 5fed3813f7f5
245 245
246 246 - fails when cert doesn't match hostname (port is ignored)
247 247 $ hg -R copy-pull id https://localhost:$HGPORT1/
248 248 abort: certificate for localhost has unexpected fingerprint 28:ff:71:bf:65:31:14:23:ad:62:92:b4:0e:31:99:18:fc:83:e3:9b
249 249 (check hostfingerprint configuration)
250 250 [255]
251 251
252 252
253 253 - ignores that certificate doesn't match hostname
254 254 $ hg -R copy-pull id https://127.0.0.1:$HGPORT/
255 255 5fed3813f7f5
256 256
257 257 HGPORT1 is reused below for tinyproxy tests. Kill that server.
258 258 $ "$TESTDIR/killdaemons.py" hg1.pid
259 259
260 260 Prepare for connecting through proxy
261 261
262 262 $ "$TESTDIR/tinyproxy.py" $HGPORT1 localhost >proxy.log </dev/null 2>&1 &
263 263 $ while [ ! -f proxy.pid ]; do sleep 0; done
264 264 $ cat proxy.pid >> $DAEMON_PIDS
265 265
266 266 $ echo "[http_proxy]" >> copy-pull/.hg/hgrc
267 267 $ echo "always=True" >> copy-pull/.hg/hgrc
268 268 $ echo "[hostfingerprints]" >> copy-pull/.hg/hgrc
269 269 $ echo "localhost =" >> copy-pull/.hg/hgrc
270 270
271 271 Test unvalidated https through proxy
272 272
273 273 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull --insecure --traceback
274 274 pulling from https://localhost:$HGPORT/
275 275 warning: localhost certificate with fingerprint 91:4f:1a:ff:87:24:9c:09:b6:85:9b:88:b1:90:6d:30:75:64:91:ca not verified (check hostfingerprints or web.cacerts config setting)
276 276 searching for changes
277 277 no changes found
278 278
279 279 Test https with cacert and fingerprint through proxy
280 280
281 281 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull --config web.cacerts=pub.pem
282 282 pulling from https://localhost:$HGPORT/
283 283 searching for changes
284 284 no changes found
285 285 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull https://127.0.0.1:$HGPORT/
286 286 pulling from https://127.0.0.1:$HGPORT/
287 287 searching for changes
288 288 no changes found
289 289
290 290 Test https with cert problems through proxy
291 291
292 292 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull --config web.cacerts=pub-other.pem
293 293 pulling from https://localhost:$HGPORT/
294 294 abort: error: *certificate verify failed* (glob)
295 295 [255]
296 296 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull --config web.cacerts=pub-expired.pem https://localhost:$HGPORT2/
297 297 pulling from https://localhost:$HGPORT2/
298 298 abort: error: *certificate verify failed* (glob)
299 299 [255]
General Comments 0
You need to be logged in to leave comments. Login now