##// END OF EJS Templates
url: remember http password database in ui object...
liscju -
r29378:fea71f66 default
parent child Browse files
Show More
@@ -1,1355 +1,1362
1 1 # ui.py - user interface bits 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 __future__ import absolute_import
9 9
10 10 import errno
11 11 import getpass
12 12 import inspect
13 13 import os
14 14 import re
15 15 import socket
16 16 import sys
17 17 import tempfile
18 18 import traceback
19 19
20 20 from .i18n import _
21 21 from .node import hex
22 22
23 23 from . import (
24 24 config,
25 25 error,
26 26 formatter,
27 27 progress,
28 28 scmutil,
29 29 util,
30 30 )
31 31
32 urlreq = util.urlreq
33
32 34 samplehgrcs = {
33 35 'user':
34 36 """# example user config (see "hg help config" for more info)
35 37 [ui]
36 38 # name and email, e.g.
37 39 # username = Jane Doe <jdoe@example.com>
38 40 username =
39 41
40 42 [extensions]
41 43 # uncomment these lines to enable some popular extensions
42 44 # (see "hg help extensions" for more info)
43 45 #
44 46 # pager =
45 47 # color =""",
46 48
47 49 'cloned':
48 50 """# example repository config (see "hg help config" for more info)
49 51 [paths]
50 52 default = %s
51 53
52 54 # path aliases to other clones of this repo in URLs or filesystem paths
53 55 # (see "hg help config.paths" for more info)
54 56 #
55 57 # default-push = ssh://jdoe@example.net/hg/jdoes-fork
56 58 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
57 59 # my-clone = /home/jdoe/jdoes-clone
58 60
59 61 [ui]
60 62 # name and email (local to this repository, optional), e.g.
61 63 # username = Jane Doe <jdoe@example.com>
62 64 """,
63 65
64 66 'local':
65 67 """# example repository config (see "hg help config" for more info)
66 68 [paths]
67 69 # path aliases to other clones of this repo in URLs or filesystem paths
68 70 # (see "hg help config.paths" for more info)
69 71 #
70 72 # default = http://example.com/hg/example-repo
71 73 # default-push = ssh://jdoe@example.net/hg/jdoes-fork
72 74 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
73 75 # my-clone = /home/jdoe/jdoes-clone
74 76
75 77 [ui]
76 78 # name and email (local to this repository, optional), e.g.
77 79 # username = Jane Doe <jdoe@example.com>
78 80 """,
79 81
80 82 'global':
81 83 """# example system-wide hg config (see "hg help config" for more info)
82 84
83 85 [extensions]
84 86 # uncomment these lines to enable some popular extensions
85 87 # (see "hg help extensions" for more info)
86 88 #
87 89 # blackbox =
88 90 # color =
89 91 # pager =""",
90 92 }
91 93
92 94 class ui(object):
93 95 def __init__(self, src=None):
94 96 # _buffers: used for temporary capture of output
95 97 self._buffers = []
96 98 # 3-tuple describing how each buffer in the stack behaves.
97 99 # Values are (capture stderr, capture subprocesses, apply labels).
98 100 self._bufferstates = []
99 101 # When a buffer is active, defines whether we are expanding labels.
100 102 # This exists to prevent an extra list lookup.
101 103 self._bufferapplylabels = None
102 104 self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
103 105 self._reportuntrusted = True
104 106 self._ocfg = config.config() # overlay
105 107 self._tcfg = config.config() # trusted
106 108 self._ucfg = config.config() # untrusted
107 109 self._trustusers = set()
108 110 self._trustgroups = set()
109 111 self.callhooks = True
110 112 # Insecure server connections requested.
111 113 self.insecureconnections = False
112 114
113 115 if src:
114 116 self.fout = src.fout
115 117 self.ferr = src.ferr
116 118 self.fin = src.fin
117 119
118 120 self._tcfg = src._tcfg.copy()
119 121 self._ucfg = src._ucfg.copy()
120 122 self._ocfg = src._ocfg.copy()
121 123 self._trustusers = src._trustusers.copy()
122 124 self._trustgroups = src._trustgroups.copy()
123 125 self.environ = src.environ
124 126 self.callhooks = src.callhooks
125 127 self.insecureconnections = src.insecureconnections
126 128 self.fixconfig()
129
130 self.httppasswordmgrdb = src.httppasswordmgrdb
127 131 else:
128 132 self.fout = sys.stdout
129 133 self.ferr = sys.stderr
130 134 self.fin = sys.stdin
131 135
132 136 # shared read-only environment
133 137 self.environ = os.environ
134 138 # we always trust global config files
135 139 for f in scmutil.rcpath():
136 140 self.readconfig(f, trust=True)
137 141
142 self.httppasswordmgrdb = urlreq.httppasswordmgrwithdefaultrealm()
143
138 144 def copy(self):
139 145 return self.__class__(self)
140 146
141 147 def resetstate(self):
142 148 """Clear internal state that shouldn't persist across commands"""
143 149 if self._progbar:
144 150 self._progbar.resetstate() # reset last-print time of progress bar
151 self.httppasswordmgrdb = urlreq.httppasswordmgrwithdefaultrealm()
145 152
146 153 def formatter(self, topic, opts):
147 154 return formatter.formatter(self, topic, opts)
148 155
149 156 def _trusted(self, fp, f):
150 157 st = util.fstat(fp)
151 158 if util.isowner(st):
152 159 return True
153 160
154 161 tusers, tgroups = self._trustusers, self._trustgroups
155 162 if '*' in tusers or '*' in tgroups:
156 163 return True
157 164
158 165 user = util.username(st.st_uid)
159 166 group = util.groupname(st.st_gid)
160 167 if user in tusers or group in tgroups or user == util.username():
161 168 return True
162 169
163 170 if self._reportuntrusted:
164 171 self.warn(_('not trusting file %s from untrusted '
165 172 'user %s, group %s\n') % (f, user, group))
166 173 return False
167 174
168 175 def readconfig(self, filename, root=None, trust=False,
169 176 sections=None, remap=None):
170 177 try:
171 178 fp = open(filename)
172 179 except IOError:
173 180 if not sections: # ignore unless we were looking for something
174 181 return
175 182 raise
176 183
177 184 cfg = config.config()
178 185 trusted = sections or trust or self._trusted(fp, filename)
179 186
180 187 try:
181 188 cfg.read(filename, fp, sections=sections, remap=remap)
182 189 fp.close()
183 190 except error.ConfigError as inst:
184 191 if trusted:
185 192 raise
186 193 self.warn(_("ignored: %s\n") % str(inst))
187 194
188 195 if self.plain():
189 196 for k in ('debug', 'fallbackencoding', 'quiet', 'slash',
190 197 'logtemplate', 'statuscopies', 'style',
191 198 'traceback', 'verbose'):
192 199 if k in cfg['ui']:
193 200 del cfg['ui'][k]
194 201 for k, v in cfg.items('defaults'):
195 202 del cfg['defaults'][k]
196 203 # Don't remove aliases from the configuration if in the exceptionlist
197 204 if self.plain('alias'):
198 205 for k, v in cfg.items('alias'):
199 206 del cfg['alias'][k]
200 207 if self.plain('revsetalias'):
201 208 for k, v in cfg.items('revsetalias'):
202 209 del cfg['revsetalias'][k]
203 210 if self.plain('templatealias'):
204 211 for k, v in cfg.items('templatealias'):
205 212 del cfg['templatealias'][k]
206 213
207 214 if trusted:
208 215 self._tcfg.update(cfg)
209 216 self._tcfg.update(self._ocfg)
210 217 self._ucfg.update(cfg)
211 218 self._ucfg.update(self._ocfg)
212 219
213 220 if root is None:
214 221 root = os.path.expanduser('~')
215 222 self.fixconfig(root=root)
216 223
217 224 def fixconfig(self, root=None, section=None):
218 225 if section in (None, 'paths'):
219 226 # expand vars and ~
220 227 # translate paths relative to root (or home) into absolute paths
221 228 root = root or os.getcwd()
222 229 for c in self._tcfg, self._ucfg, self._ocfg:
223 230 for n, p in c.items('paths'):
224 231 if not p:
225 232 continue
226 233 if '%%' in p:
227 234 self.warn(_("(deprecated '%%' in path %s=%s from %s)\n")
228 235 % (n, p, self.configsource('paths', n)))
229 236 p = p.replace('%%', '%')
230 237 p = util.expandpath(p)
231 238 if not util.hasscheme(p) and not os.path.isabs(p):
232 239 p = os.path.normpath(os.path.join(root, p))
233 240 c.set("paths", n, p)
234 241
235 242 if section in (None, 'ui'):
236 243 # update ui options
237 244 self.debugflag = self.configbool('ui', 'debug')
238 245 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
239 246 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
240 247 if self.verbose and self.quiet:
241 248 self.quiet = self.verbose = False
242 249 self._reportuntrusted = self.debugflag or self.configbool("ui",
243 250 "report_untrusted", True)
244 251 self.tracebackflag = self.configbool('ui', 'traceback', False)
245 252
246 253 if section in (None, 'trusted'):
247 254 # update trust information
248 255 self._trustusers.update(self.configlist('trusted', 'users'))
249 256 self._trustgroups.update(self.configlist('trusted', 'groups'))
250 257
251 258 def backupconfig(self, section, item):
252 259 return (self._ocfg.backup(section, item),
253 260 self._tcfg.backup(section, item),
254 261 self._ucfg.backup(section, item),)
255 262 def restoreconfig(self, data):
256 263 self._ocfg.restore(data[0])
257 264 self._tcfg.restore(data[1])
258 265 self._ucfg.restore(data[2])
259 266
260 267 def setconfig(self, section, name, value, source=''):
261 268 for cfg in (self._ocfg, self._tcfg, self._ucfg):
262 269 cfg.set(section, name, value, source)
263 270 self.fixconfig(section=section)
264 271
265 272 def _data(self, untrusted):
266 273 return untrusted and self._ucfg or self._tcfg
267 274
268 275 def configsource(self, section, name, untrusted=False):
269 276 return self._data(untrusted).source(section, name) or 'none'
270 277
271 278 def config(self, section, name, default=None, untrusted=False):
272 279 if isinstance(name, list):
273 280 alternates = name
274 281 else:
275 282 alternates = [name]
276 283
277 284 for n in alternates:
278 285 value = self._data(untrusted).get(section, n, None)
279 286 if value is not None:
280 287 name = n
281 288 break
282 289 else:
283 290 value = default
284 291
285 292 if self.debugflag and not untrusted and self._reportuntrusted:
286 293 for n in alternates:
287 294 uvalue = self._ucfg.get(section, n)
288 295 if uvalue is not None and uvalue != value:
289 296 self.debug("ignoring untrusted configuration option "
290 297 "%s.%s = %s\n" % (section, n, uvalue))
291 298 return value
292 299
293 300 def configsuboptions(self, section, name, default=None, untrusted=False):
294 301 """Get a config option and all sub-options.
295 302
296 303 Some config options have sub-options that are declared with the
297 304 format "key:opt = value". This method is used to return the main
298 305 option and all its declared sub-options.
299 306
300 307 Returns a 2-tuple of ``(option, sub-options)``, where `sub-options``
301 308 is a dict of defined sub-options where keys and values are strings.
302 309 """
303 310 data = self._data(untrusted)
304 311 main = data.get(section, name, default)
305 312 if self.debugflag and not untrusted and self._reportuntrusted:
306 313 uvalue = self._ucfg.get(section, name)
307 314 if uvalue is not None and uvalue != main:
308 315 self.debug('ignoring untrusted configuration option '
309 316 '%s.%s = %s\n' % (section, name, uvalue))
310 317
311 318 sub = {}
312 319 prefix = '%s:' % name
313 320 for k, v in data.items(section):
314 321 if k.startswith(prefix):
315 322 sub[k[len(prefix):]] = v
316 323
317 324 if self.debugflag and not untrusted and self._reportuntrusted:
318 325 for k, v in sub.items():
319 326 uvalue = self._ucfg.get(section, '%s:%s' % (name, k))
320 327 if uvalue is not None and uvalue != v:
321 328 self.debug('ignoring untrusted configuration option '
322 329 '%s:%s.%s = %s\n' % (section, name, k, uvalue))
323 330
324 331 return main, sub
325 332
326 333 def configpath(self, section, name, default=None, untrusted=False):
327 334 'get a path config item, expanded relative to repo root or config file'
328 335 v = self.config(section, name, default, untrusted)
329 336 if v is None:
330 337 return None
331 338 if not os.path.isabs(v) or "://" not in v:
332 339 src = self.configsource(section, name, untrusted)
333 340 if ':' in src:
334 341 base = os.path.dirname(src.rsplit(':')[0])
335 342 v = os.path.join(base, os.path.expanduser(v))
336 343 return v
337 344
338 345 def configbool(self, section, name, default=False, untrusted=False):
339 346 """parse a configuration element as a boolean
340 347
341 348 >>> u = ui(); s = 'foo'
342 349 >>> u.setconfig(s, 'true', 'yes')
343 350 >>> u.configbool(s, 'true')
344 351 True
345 352 >>> u.setconfig(s, 'false', 'no')
346 353 >>> u.configbool(s, 'false')
347 354 False
348 355 >>> u.configbool(s, 'unknown')
349 356 False
350 357 >>> u.configbool(s, 'unknown', True)
351 358 True
352 359 >>> u.setconfig(s, 'invalid', 'somevalue')
353 360 >>> u.configbool(s, 'invalid')
354 361 Traceback (most recent call last):
355 362 ...
356 363 ConfigError: foo.invalid is not a boolean ('somevalue')
357 364 """
358 365
359 366 v = self.config(section, name, None, untrusted)
360 367 if v is None:
361 368 return default
362 369 if isinstance(v, bool):
363 370 return v
364 371 b = util.parsebool(v)
365 372 if b is None:
366 373 raise error.ConfigError(_("%s.%s is not a boolean ('%s')")
367 374 % (section, name, v))
368 375 return b
369 376
370 377 def configint(self, section, name, default=None, untrusted=False):
371 378 """parse a configuration element as an integer
372 379
373 380 >>> u = ui(); s = 'foo'
374 381 >>> u.setconfig(s, 'int1', '42')
375 382 >>> u.configint(s, 'int1')
376 383 42
377 384 >>> u.setconfig(s, 'int2', '-42')
378 385 >>> u.configint(s, 'int2')
379 386 -42
380 387 >>> u.configint(s, 'unknown', 7)
381 388 7
382 389 >>> u.setconfig(s, 'invalid', 'somevalue')
383 390 >>> u.configint(s, 'invalid')
384 391 Traceback (most recent call last):
385 392 ...
386 393 ConfigError: foo.invalid is not an integer ('somevalue')
387 394 """
388 395
389 396 v = self.config(section, name, None, untrusted)
390 397 if v is None:
391 398 return default
392 399 try:
393 400 return int(v)
394 401 except ValueError:
395 402 raise error.ConfigError(_("%s.%s is not an integer ('%s')")
396 403 % (section, name, v))
397 404
398 405 def configbytes(self, section, name, default=0, untrusted=False):
399 406 """parse a configuration element as a quantity in bytes
400 407
401 408 Units can be specified as b (bytes), k or kb (kilobytes), m or
402 409 mb (megabytes), g or gb (gigabytes).
403 410
404 411 >>> u = ui(); s = 'foo'
405 412 >>> u.setconfig(s, 'val1', '42')
406 413 >>> u.configbytes(s, 'val1')
407 414 42
408 415 >>> u.setconfig(s, 'val2', '42.5 kb')
409 416 >>> u.configbytes(s, 'val2')
410 417 43520
411 418 >>> u.configbytes(s, 'unknown', '7 MB')
412 419 7340032
413 420 >>> u.setconfig(s, 'invalid', 'somevalue')
414 421 >>> u.configbytes(s, 'invalid')
415 422 Traceback (most recent call last):
416 423 ...
417 424 ConfigError: foo.invalid is not a byte quantity ('somevalue')
418 425 """
419 426
420 427 value = self.config(section, name)
421 428 if value is None:
422 429 if not isinstance(default, str):
423 430 return default
424 431 value = default
425 432 try:
426 433 return util.sizetoint(value)
427 434 except error.ParseError:
428 435 raise error.ConfigError(_("%s.%s is not a byte quantity ('%s')")
429 436 % (section, name, value))
430 437
431 438 def configlist(self, section, name, default=None, untrusted=False):
432 439 """parse a configuration element as a list of comma/space separated
433 440 strings
434 441
435 442 >>> u = ui(); s = 'foo'
436 443 >>> u.setconfig(s, 'list1', 'this,is "a small" ,test')
437 444 >>> u.configlist(s, 'list1')
438 445 ['this', 'is', 'a small', 'test']
439 446 """
440 447
441 448 def _parse_plain(parts, s, offset):
442 449 whitespace = False
443 450 while offset < len(s) and (s[offset].isspace() or s[offset] == ','):
444 451 whitespace = True
445 452 offset += 1
446 453 if offset >= len(s):
447 454 return None, parts, offset
448 455 if whitespace:
449 456 parts.append('')
450 457 if s[offset] == '"' and not parts[-1]:
451 458 return _parse_quote, parts, offset + 1
452 459 elif s[offset] == '"' and parts[-1][-1] == '\\':
453 460 parts[-1] = parts[-1][:-1] + s[offset]
454 461 return _parse_plain, parts, offset + 1
455 462 parts[-1] += s[offset]
456 463 return _parse_plain, parts, offset + 1
457 464
458 465 def _parse_quote(parts, s, offset):
459 466 if offset < len(s) and s[offset] == '"': # ""
460 467 parts.append('')
461 468 offset += 1
462 469 while offset < len(s) and (s[offset].isspace() or
463 470 s[offset] == ','):
464 471 offset += 1
465 472 return _parse_plain, parts, offset
466 473
467 474 while offset < len(s) and s[offset] != '"':
468 475 if (s[offset] == '\\' and offset + 1 < len(s)
469 476 and s[offset + 1] == '"'):
470 477 offset += 1
471 478 parts[-1] += '"'
472 479 else:
473 480 parts[-1] += s[offset]
474 481 offset += 1
475 482
476 483 if offset >= len(s):
477 484 real_parts = _configlist(parts[-1])
478 485 if not real_parts:
479 486 parts[-1] = '"'
480 487 else:
481 488 real_parts[0] = '"' + real_parts[0]
482 489 parts = parts[:-1]
483 490 parts.extend(real_parts)
484 491 return None, parts, offset
485 492
486 493 offset += 1
487 494 while offset < len(s) and s[offset] in [' ', ',']:
488 495 offset += 1
489 496
490 497 if offset < len(s):
491 498 if offset + 1 == len(s) and s[offset] == '"':
492 499 parts[-1] += '"'
493 500 offset += 1
494 501 else:
495 502 parts.append('')
496 503 else:
497 504 return None, parts, offset
498 505
499 506 return _parse_plain, parts, offset
500 507
501 508 def _configlist(s):
502 509 s = s.rstrip(' ,')
503 510 if not s:
504 511 return []
505 512 parser, parts, offset = _parse_plain, [''], 0
506 513 while parser:
507 514 parser, parts, offset = parser(parts, s, offset)
508 515 return parts
509 516
510 517 result = self.config(section, name, untrusted=untrusted)
511 518 if result is None:
512 519 result = default or []
513 520 if isinstance(result, basestring):
514 521 result = _configlist(result.lstrip(' ,\n'))
515 522 if result is None:
516 523 result = default or []
517 524 return result
518 525
519 526 def hasconfig(self, section, name, untrusted=False):
520 527 return self._data(untrusted).hasitem(section, name)
521 528
522 529 def has_section(self, section, untrusted=False):
523 530 '''tell whether section exists in config.'''
524 531 return section in self._data(untrusted)
525 532
526 533 def configitems(self, section, untrusted=False, ignoresub=False):
527 534 items = self._data(untrusted).items(section)
528 535 if ignoresub:
529 536 newitems = {}
530 537 for k, v in items:
531 538 if ':' not in k:
532 539 newitems[k] = v
533 540 items = newitems.items()
534 541 if self.debugflag and not untrusted and self._reportuntrusted:
535 542 for k, v in self._ucfg.items(section):
536 543 if self._tcfg.get(section, k) != v:
537 544 self.debug("ignoring untrusted configuration option "
538 545 "%s.%s = %s\n" % (section, k, v))
539 546 return items
540 547
541 548 def walkconfig(self, untrusted=False):
542 549 cfg = self._data(untrusted)
543 550 for section in cfg.sections():
544 551 for name, value in self.configitems(section, untrusted):
545 552 yield section, name, value
546 553
547 554 def plain(self, feature=None):
548 555 '''is plain mode active?
549 556
550 557 Plain mode means that all configuration variables which affect
551 558 the behavior and output of Mercurial should be
552 559 ignored. Additionally, the output should be stable,
553 560 reproducible and suitable for use in scripts or applications.
554 561
555 562 The only way to trigger plain mode is by setting either the
556 563 `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
557 564
558 565 The return value can either be
559 566 - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
560 567 - True otherwise
561 568 '''
562 569 if 'HGPLAIN' not in os.environ and 'HGPLAINEXCEPT' not in os.environ:
563 570 return False
564 571 exceptions = os.environ.get('HGPLAINEXCEPT', '').strip().split(',')
565 572 if feature and exceptions:
566 573 return feature not in exceptions
567 574 return True
568 575
569 576 def username(self):
570 577 """Return default username to be used in commits.
571 578
572 579 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
573 580 and stop searching if one of these is set.
574 581 If not found and ui.askusername is True, ask the user, else use
575 582 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
576 583 """
577 584 user = os.environ.get("HGUSER")
578 585 if user is None:
579 586 user = self.config("ui", ["username", "user"])
580 587 if user is not None:
581 588 user = os.path.expandvars(user)
582 589 if user is None:
583 590 user = os.environ.get("EMAIL")
584 591 if user is None and self.configbool("ui", "askusername"):
585 592 user = self.prompt(_("enter a commit username:"), default=None)
586 593 if user is None and not self.interactive():
587 594 try:
588 595 user = '%s@%s' % (util.getuser(), socket.getfqdn())
589 596 self.warn(_("no username found, using '%s' instead\n") % user)
590 597 except KeyError:
591 598 pass
592 599 if not user:
593 600 raise error.Abort(_('no username supplied'),
594 601 hint=_("use 'hg config --edit' "
595 602 'to set your username'))
596 603 if "\n" in user:
597 604 raise error.Abort(_("username %s contains a newline\n")
598 605 % repr(user))
599 606 return user
600 607
601 608 def shortuser(self, user):
602 609 """Return a short representation of a user name or email address."""
603 610 if not self.verbose:
604 611 user = util.shortuser(user)
605 612 return user
606 613
607 614 def expandpath(self, loc, default=None):
608 615 """Return repository location relative to cwd or from [paths]"""
609 616 try:
610 617 p = self.paths.getpath(loc)
611 618 if p:
612 619 return p.rawloc
613 620 except error.RepoError:
614 621 pass
615 622
616 623 if default:
617 624 try:
618 625 p = self.paths.getpath(default)
619 626 if p:
620 627 return p.rawloc
621 628 except error.RepoError:
622 629 pass
623 630
624 631 return loc
625 632
626 633 @util.propertycache
627 634 def paths(self):
628 635 return paths(self)
629 636
630 637 def pushbuffer(self, error=False, subproc=False, labeled=False):
631 638 """install a buffer to capture standard output of the ui object
632 639
633 640 If error is True, the error output will be captured too.
634 641
635 642 If subproc is True, output from subprocesses (typically hooks) will be
636 643 captured too.
637 644
638 645 If labeled is True, any labels associated with buffered
639 646 output will be handled. By default, this has no effect
640 647 on the output returned, but extensions and GUI tools may
641 648 handle this argument and returned styled output. If output
642 649 is being buffered so it can be captured and parsed or
643 650 processed, labeled should not be set to True.
644 651 """
645 652 self._buffers.append([])
646 653 self._bufferstates.append((error, subproc, labeled))
647 654 self._bufferapplylabels = labeled
648 655
649 656 def popbuffer(self):
650 657 '''pop the last buffer and return the buffered output'''
651 658 self._bufferstates.pop()
652 659 if self._bufferstates:
653 660 self._bufferapplylabels = self._bufferstates[-1][2]
654 661 else:
655 662 self._bufferapplylabels = None
656 663
657 664 return "".join(self._buffers.pop())
658 665
659 666 def write(self, *args, **opts):
660 667 '''write args to output
661 668
662 669 By default, this method simply writes to the buffer or stdout,
663 670 but extensions or GUI tools may override this method,
664 671 write_err(), popbuffer(), and label() to style output from
665 672 various parts of hg.
666 673
667 674 An optional keyword argument, "label", can be passed in.
668 675 This should be a string containing label names separated by
669 676 space. Label names take the form of "topic.type". For example,
670 677 ui.debug() issues a label of "ui.debug".
671 678
672 679 When labeling output for a specific command, a label of
673 680 "cmdname.type" is recommended. For example, status issues
674 681 a label of "status.modified" for modified files.
675 682 '''
676 683 if self._buffers and not opts.get('prompt', False):
677 684 self._buffers[-1].extend(a for a in args)
678 685 else:
679 686 self._progclear()
680 687 for a in args:
681 688 self.fout.write(a)
682 689
683 690 def write_err(self, *args, **opts):
684 691 self._progclear()
685 692 try:
686 693 if self._bufferstates and self._bufferstates[-1][0]:
687 694 return self.write(*args, **opts)
688 695 if not getattr(self.fout, 'closed', False):
689 696 self.fout.flush()
690 697 for a in args:
691 698 self.ferr.write(a)
692 699 # stderr may be buffered under win32 when redirected to files,
693 700 # including stdout.
694 701 if not getattr(self.ferr, 'closed', False):
695 702 self.ferr.flush()
696 703 except IOError as inst:
697 704 if inst.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
698 705 raise
699 706
700 707 def flush(self):
701 708 try: self.fout.flush()
702 709 except (IOError, ValueError): pass
703 710 try: self.ferr.flush()
704 711 except (IOError, ValueError): pass
705 712
706 713 def _isatty(self, fh):
707 714 if self.configbool('ui', 'nontty', False):
708 715 return False
709 716 return util.isatty(fh)
710 717
711 718 def interface(self, feature):
712 719 """what interface to use for interactive console features?
713 720
714 721 The interface is controlled by the value of `ui.interface` but also by
715 722 the value of feature-specific configuration. For example:
716 723
717 724 ui.interface.histedit = text
718 725 ui.interface.chunkselector = curses
719 726
720 727 Here the features are "histedit" and "chunkselector".
721 728
722 729 The configuration above means that the default interfaces for commands
723 730 is curses, the interface for histedit is text and the interface for
724 731 selecting chunk is crecord (the best curses interface available).
725 732
726 733 Consider the following exemple:
727 734 ui.interface = curses
728 735 ui.interface.histedit = text
729 736
730 737 Then histedit will use the text interface and chunkselector will use
731 738 the default curses interface (crecord at the moment).
732 739 """
733 740 alldefaults = frozenset(["text", "curses"])
734 741
735 742 featureinterfaces = {
736 743 "chunkselector": [
737 744 "text",
738 745 "curses",
739 746 ]
740 747 }
741 748
742 749 # Feature-specific interface
743 750 if feature not in featureinterfaces.keys():
744 751 # Programming error, not user error
745 752 raise ValueError("Unknown feature requested %s" % feature)
746 753
747 754 availableinterfaces = frozenset(featureinterfaces[feature])
748 755 if alldefaults > availableinterfaces:
749 756 # Programming error, not user error. We need a use case to
750 757 # define the right thing to do here.
751 758 raise ValueError(
752 759 "Feature %s does not handle all default interfaces" %
753 760 feature)
754 761
755 762 if self.plain():
756 763 return "text"
757 764
758 765 # Default interface for all the features
759 766 defaultinterface = "text"
760 767 i = self.config("ui", "interface", None)
761 768 if i in alldefaults:
762 769 defaultinterface = i
763 770
764 771 choseninterface = defaultinterface
765 772 f = self.config("ui", "interface.%s" % feature, None)
766 773 if f in availableinterfaces:
767 774 choseninterface = f
768 775
769 776 if i is not None and defaultinterface != i:
770 777 if f is not None:
771 778 self.warn(_("invalid value for ui.interface: %s\n") %
772 779 (i,))
773 780 else:
774 781 self.warn(_("invalid value for ui.interface: %s (using %s)\n") %
775 782 (i, choseninterface))
776 783 if f is not None and choseninterface != f:
777 784 self.warn(_("invalid value for ui.interface.%s: %s (using %s)\n") %
778 785 (feature, f, choseninterface))
779 786
780 787 return choseninterface
781 788
782 789 def interactive(self):
783 790 '''is interactive input allowed?
784 791
785 792 An interactive session is a session where input can be reasonably read
786 793 from `sys.stdin'. If this function returns false, any attempt to read
787 794 from stdin should fail with an error, unless a sensible default has been
788 795 specified.
789 796
790 797 Interactiveness is triggered by the value of the `ui.interactive'
791 798 configuration variable or - if it is unset - when `sys.stdin' points
792 799 to a terminal device.
793 800
794 801 This function refers to input only; for output, see `ui.formatted()'.
795 802 '''
796 803 i = self.configbool("ui", "interactive", None)
797 804 if i is None:
798 805 # some environments replace stdin without implementing isatty
799 806 # usually those are non-interactive
800 807 return self._isatty(self.fin)
801 808
802 809 return i
803 810
804 811 def termwidth(self):
805 812 '''how wide is the terminal in columns?
806 813 '''
807 814 if 'COLUMNS' in os.environ:
808 815 try:
809 816 return int(os.environ['COLUMNS'])
810 817 except ValueError:
811 818 pass
812 819 return util.termwidth()
813 820
814 821 def formatted(self):
815 822 '''should formatted output be used?
816 823
817 824 It is often desirable to format the output to suite the output medium.
818 825 Examples of this are truncating long lines or colorizing messages.
819 826 However, this is not often not desirable when piping output into other
820 827 utilities, e.g. `grep'.
821 828
822 829 Formatted output is triggered by the value of the `ui.formatted'
823 830 configuration variable or - if it is unset - when `sys.stdout' points
824 831 to a terminal device. Please note that `ui.formatted' should be
825 832 considered an implementation detail; it is not intended for use outside
826 833 Mercurial or its extensions.
827 834
828 835 This function refers to output only; for input, see `ui.interactive()'.
829 836 This function always returns false when in plain mode, see `ui.plain()'.
830 837 '''
831 838 if self.plain():
832 839 return False
833 840
834 841 i = self.configbool("ui", "formatted", None)
835 842 if i is None:
836 843 # some environments replace stdout without implementing isatty
837 844 # usually those are non-interactive
838 845 return self._isatty(self.fout)
839 846
840 847 return i
841 848
842 849 def _readline(self, prompt=''):
843 850 if self._isatty(self.fin):
844 851 try:
845 852 # magically add command line editing support, where
846 853 # available
847 854 import readline
848 855 # force demandimport to really load the module
849 856 readline.read_history_file
850 857 # windows sometimes raises something other than ImportError
851 858 except Exception:
852 859 pass
853 860
854 861 # call write() so output goes through subclassed implementation
855 862 # e.g. color extension on Windows
856 863 self.write(prompt, prompt=True)
857 864
858 865 # instead of trying to emulate raw_input, swap (self.fin,
859 866 # self.fout) with (sys.stdin, sys.stdout)
860 867 oldin = sys.stdin
861 868 oldout = sys.stdout
862 869 sys.stdin = self.fin
863 870 sys.stdout = self.fout
864 871 # prompt ' ' must exist; otherwise readline may delete entire line
865 872 # - http://bugs.python.org/issue12833
866 873 line = raw_input(' ')
867 874 sys.stdin = oldin
868 875 sys.stdout = oldout
869 876
870 877 # When stdin is in binary mode on Windows, it can cause
871 878 # raw_input() to emit an extra trailing carriage return
872 879 if os.linesep == '\r\n' and line and line[-1] == '\r':
873 880 line = line[:-1]
874 881 return line
875 882
876 883 def prompt(self, msg, default="y"):
877 884 """Prompt user with msg, read response.
878 885 If ui is not interactive, the default is returned.
879 886 """
880 887 if not self.interactive():
881 888 self.write(msg, ' ', default or '', "\n")
882 889 return default
883 890 try:
884 891 r = self._readline(self.label(msg, 'ui.prompt'))
885 892 if not r:
886 893 r = default
887 894 if self.configbool('ui', 'promptecho'):
888 895 self.write(r, "\n")
889 896 return r
890 897 except EOFError:
891 898 raise error.ResponseExpected()
892 899
893 900 @staticmethod
894 901 def extractchoices(prompt):
895 902 """Extract prompt message and list of choices from specified prompt.
896 903
897 904 This returns tuple "(message, choices)", and "choices" is the
898 905 list of tuple "(response character, text without &)".
899 906
900 907 >>> ui.extractchoices("awake? $$ &Yes $$ &No")
901 908 ('awake? ', [('y', 'Yes'), ('n', 'No')])
902 909 >>> ui.extractchoices("line\\nbreak? $$ &Yes $$ &No")
903 910 ('line\\nbreak? ', [('y', 'Yes'), ('n', 'No')])
904 911 >>> ui.extractchoices("want lots of $$money$$?$$Ye&s$$N&o")
905 912 ('want lots of $$money$$?', [('s', 'Yes'), ('o', 'No')])
906 913 """
907 914
908 915 # Sadly, the prompt string may have been built with a filename
909 916 # containing "$$" so let's try to find the first valid-looking
910 917 # prompt to start parsing. Sadly, we also can't rely on
911 918 # choices containing spaces, ASCII, or basically anything
912 919 # except an ampersand followed by a character.
913 920 m = re.match(r'(?s)(.+?)\$\$([^\$]*&[^ \$].*)', prompt)
914 921 msg = m.group(1)
915 922 choices = [p.strip(' ') for p in m.group(2).split('$$')]
916 923 return (msg,
917 924 [(s[s.index('&') + 1].lower(), s.replace('&', '', 1))
918 925 for s in choices])
919 926
920 927 def promptchoice(self, prompt, default=0):
921 928 """Prompt user with a message, read response, and ensure it matches
922 929 one of the provided choices. The prompt is formatted as follows:
923 930
924 931 "would you like fries with that (Yn)? $$ &Yes $$ &No"
925 932
926 933 The index of the choice is returned. Responses are case
927 934 insensitive. If ui is not interactive, the default is
928 935 returned.
929 936 """
930 937
931 938 msg, choices = self.extractchoices(prompt)
932 939 resps = [r for r, t in choices]
933 940 while True:
934 941 r = self.prompt(msg, resps[default])
935 942 if r.lower() in resps:
936 943 return resps.index(r.lower())
937 944 self.write(_("unrecognized response\n"))
938 945
939 946 def getpass(self, prompt=None, default=None):
940 947 if not self.interactive():
941 948 return default
942 949 try:
943 950 self.write_err(self.label(prompt or _('password: '), 'ui.prompt'))
944 951 # disable getpass() only if explicitly specified. it's still valid
945 952 # to interact with tty even if fin is not a tty.
946 953 if self.configbool('ui', 'nontty'):
947 954 return self.fin.readline().rstrip('\n')
948 955 else:
949 956 return getpass.getpass('')
950 957 except EOFError:
951 958 raise error.ResponseExpected()
952 959 def status(self, *msg, **opts):
953 960 '''write status message to output (if ui.quiet is False)
954 961
955 962 This adds an output label of "ui.status".
956 963 '''
957 964 if not self.quiet:
958 965 opts['label'] = opts.get('label', '') + ' ui.status'
959 966 self.write(*msg, **opts)
960 967 def warn(self, *msg, **opts):
961 968 '''write warning message to output (stderr)
962 969
963 970 This adds an output label of "ui.warning".
964 971 '''
965 972 opts['label'] = opts.get('label', '') + ' ui.warning'
966 973 self.write_err(*msg, **opts)
967 974 def note(self, *msg, **opts):
968 975 '''write note to output (if ui.verbose is True)
969 976
970 977 This adds an output label of "ui.note".
971 978 '''
972 979 if self.verbose:
973 980 opts['label'] = opts.get('label', '') + ' ui.note'
974 981 self.write(*msg, **opts)
975 982 def debug(self, *msg, **opts):
976 983 '''write debug message to output (if ui.debugflag is True)
977 984
978 985 This adds an output label of "ui.debug".
979 986 '''
980 987 if self.debugflag:
981 988 opts['label'] = opts.get('label', '') + ' ui.debug'
982 989 self.write(*msg, **opts)
983 990
984 991 def edit(self, text, user, extra=None, editform=None, pending=None):
985 992 extra_defaults = {
986 993 'prefix': 'editor',
987 994 'suffix': '.txt',
988 995 }
989 996 if extra is not None:
990 997 extra_defaults.update(extra)
991 998 extra = extra_defaults
992 999 (fd, name) = tempfile.mkstemp(prefix='hg-' + extra['prefix'] + '-',
993 1000 suffix=extra['suffix'], text=True)
994 1001 try:
995 1002 f = os.fdopen(fd, "w")
996 1003 f.write(text)
997 1004 f.close()
998 1005
999 1006 environ = {'HGUSER': user}
1000 1007 if 'transplant_source' in extra:
1001 1008 environ.update({'HGREVISION': hex(extra['transplant_source'])})
1002 1009 for label in ('intermediate-source', 'source', 'rebase_source'):
1003 1010 if label in extra:
1004 1011 environ.update({'HGREVISION': extra[label]})
1005 1012 break
1006 1013 if editform:
1007 1014 environ.update({'HGEDITFORM': editform})
1008 1015 if pending:
1009 1016 environ.update({'HG_PENDING': pending})
1010 1017
1011 1018 editor = self.geteditor()
1012 1019
1013 1020 self.system("%s \"%s\"" % (editor, name),
1014 1021 environ=environ,
1015 1022 onerr=error.Abort, errprefix=_("edit failed"))
1016 1023
1017 1024 f = open(name)
1018 1025 t = f.read()
1019 1026 f.close()
1020 1027 finally:
1021 1028 os.unlink(name)
1022 1029
1023 1030 return t
1024 1031
1025 1032 def system(self, cmd, environ=None, cwd=None, onerr=None, errprefix=None):
1026 1033 '''execute shell command with appropriate output stream. command
1027 1034 output will be redirected if fout is not stdout.
1028 1035 '''
1029 1036 out = self.fout
1030 1037 if any(s[1] for s in self._bufferstates):
1031 1038 out = self
1032 1039 return util.system(cmd, environ=environ, cwd=cwd, onerr=onerr,
1033 1040 errprefix=errprefix, out=out)
1034 1041
1035 1042 def traceback(self, exc=None, force=False):
1036 1043 '''print exception traceback if traceback printing enabled or forced.
1037 1044 only to call in exception handler. returns true if traceback
1038 1045 printed.'''
1039 1046 if self.tracebackflag or force:
1040 1047 if exc is None:
1041 1048 exc = sys.exc_info()
1042 1049 cause = getattr(exc[1], 'cause', None)
1043 1050
1044 1051 if cause is not None:
1045 1052 causetb = traceback.format_tb(cause[2])
1046 1053 exctb = traceback.format_tb(exc[2])
1047 1054 exconly = traceback.format_exception_only(cause[0], cause[1])
1048 1055
1049 1056 # exclude frame where 'exc' was chained and rethrown from exctb
1050 1057 self.write_err('Traceback (most recent call last):\n',
1051 1058 ''.join(exctb[:-1]),
1052 1059 ''.join(causetb),
1053 1060 ''.join(exconly))
1054 1061 else:
1055 1062 output = traceback.format_exception(exc[0], exc[1], exc[2])
1056 1063 self.write_err(''.join(output))
1057 1064 return self.tracebackflag or force
1058 1065
1059 1066 def geteditor(self):
1060 1067 '''return editor to use'''
1061 1068 if sys.platform == 'plan9':
1062 1069 # vi is the MIPS instruction simulator on Plan 9. We
1063 1070 # instead default to E to plumb commit messages to
1064 1071 # avoid confusion.
1065 1072 editor = 'E'
1066 1073 else:
1067 1074 editor = 'vi'
1068 1075 return (os.environ.get("HGEDITOR") or
1069 1076 self.config("ui", "editor") or
1070 1077 os.environ.get("VISUAL") or
1071 1078 os.environ.get("EDITOR", editor))
1072 1079
1073 1080 @util.propertycache
1074 1081 def _progbar(self):
1075 1082 """setup the progbar singleton to the ui object"""
1076 1083 if (self.quiet or self.debugflag
1077 1084 or self.configbool('progress', 'disable', False)
1078 1085 or not progress.shouldprint(self)):
1079 1086 return None
1080 1087 return getprogbar(self)
1081 1088
1082 1089 def _progclear(self):
1083 1090 """clear progress bar output if any. use it before any output"""
1084 1091 if '_progbar' not in vars(self): # nothing loaded yet
1085 1092 return
1086 1093 if self._progbar is not None and self._progbar.printed:
1087 1094 self._progbar.clear()
1088 1095
1089 1096 def progress(self, topic, pos, item="", unit="", total=None):
1090 1097 '''show a progress message
1091 1098
1092 1099 By default a textual progress bar will be displayed if an operation
1093 1100 takes too long. 'topic' is the current operation, 'item' is a
1094 1101 non-numeric marker of the current position (i.e. the currently
1095 1102 in-process file), 'pos' is the current numeric position (i.e.
1096 1103 revision, bytes, etc.), unit is a corresponding unit label,
1097 1104 and total is the highest expected pos.
1098 1105
1099 1106 Multiple nested topics may be active at a time.
1100 1107
1101 1108 All topics should be marked closed by setting pos to None at
1102 1109 termination.
1103 1110 '''
1104 1111 if self._progbar is not None:
1105 1112 self._progbar.progress(topic, pos, item=item, unit=unit,
1106 1113 total=total)
1107 1114 if pos is None or not self.configbool('progress', 'debug'):
1108 1115 return
1109 1116
1110 1117 if unit:
1111 1118 unit = ' ' + unit
1112 1119 if item:
1113 1120 item = ' ' + item
1114 1121
1115 1122 if total:
1116 1123 pct = 100.0 * pos / total
1117 1124 self.debug('%s:%s %s/%s%s (%4.2f%%)\n'
1118 1125 % (topic, item, pos, total, unit, pct))
1119 1126 else:
1120 1127 self.debug('%s:%s %s%s\n' % (topic, item, pos, unit))
1121 1128
1122 1129 def log(self, service, *msg, **opts):
1123 1130 '''hook for logging facility extensions
1124 1131
1125 1132 service should be a readily-identifiable subsystem, which will
1126 1133 allow filtering.
1127 1134
1128 1135 *msg should be a newline-terminated format string to log, and
1129 1136 then any values to %-format into that format string.
1130 1137
1131 1138 **opts currently has no defined meanings.
1132 1139 '''
1133 1140
1134 1141 def label(self, msg, label):
1135 1142 '''style msg based on supplied label
1136 1143
1137 1144 Like ui.write(), this just returns msg unchanged, but extensions
1138 1145 and GUI tools can override it to allow styling output without
1139 1146 writing it.
1140 1147
1141 1148 ui.write(s, 'label') is equivalent to
1142 1149 ui.write(ui.label(s, 'label')).
1143 1150 '''
1144 1151 return msg
1145 1152
1146 1153 def develwarn(self, msg, stacklevel=1, config=None):
1147 1154 """issue a developer warning message
1148 1155
1149 1156 Use 'stacklevel' to report the offender some layers further up in the
1150 1157 stack.
1151 1158 """
1152 1159 if not self.configbool('devel', 'all-warnings'):
1153 1160 if config is not None and not self.configbool('devel', config):
1154 1161 return
1155 1162 msg = 'devel-warn: ' + msg
1156 1163 stacklevel += 1 # get in develwarn
1157 1164 if self.tracebackflag:
1158 1165 util.debugstacktrace(msg, stacklevel, self.ferr, self.fout)
1159 1166 self.log('develwarn', '%s at:\n%s' %
1160 1167 (msg, ''.join(util.getstackframes(stacklevel))))
1161 1168 else:
1162 1169 curframe = inspect.currentframe()
1163 1170 calframe = inspect.getouterframes(curframe, 2)
1164 1171 self.write_err('%s at: %s:%s (%s)\n'
1165 1172 % ((msg,) + calframe[stacklevel][1:4]))
1166 1173 self.log('develwarn', '%s at: %s:%s (%s)\n',
1167 1174 msg, *calframe[stacklevel][1:4])
1168 1175
1169 1176 def deprecwarn(self, msg, version):
1170 1177 """issue a deprecation warning
1171 1178
1172 1179 - msg: message explaining what is deprecated and how to upgrade,
1173 1180 - version: last version where the API will be supported,
1174 1181 """
1175 1182 if not (self.configbool('devel', 'all-warnings')
1176 1183 or self.configbool('devel', 'deprec-warn')):
1177 1184 return
1178 1185 msg += ("\n(compatibility will be dropped after Mercurial-%s,"
1179 1186 " update your code.)") % version
1180 1187 self.develwarn(msg, stacklevel=2, config='deprec-warn')
1181 1188
1182 1189 class paths(dict):
1183 1190 """Represents a collection of paths and their configs.
1184 1191
1185 1192 Data is initially derived from ui instances and the config files they have
1186 1193 loaded.
1187 1194 """
1188 1195 def __init__(self, ui):
1189 1196 dict.__init__(self)
1190 1197
1191 1198 for name, loc in ui.configitems('paths', ignoresub=True):
1192 1199 # No location is the same as not existing.
1193 1200 if not loc:
1194 1201 continue
1195 1202 loc, sub = ui.configsuboptions('paths', name)
1196 1203 self[name] = path(ui, name, rawloc=loc, suboptions=sub)
1197 1204
1198 1205 def getpath(self, name, default=None):
1199 1206 """Return a ``path`` from a string, falling back to default.
1200 1207
1201 1208 ``name`` can be a named path or locations. Locations are filesystem
1202 1209 paths or URIs.
1203 1210
1204 1211 Returns None if ``name`` is not a registered path, a URI, or a local
1205 1212 path to a repo.
1206 1213 """
1207 1214 # Only fall back to default if no path was requested.
1208 1215 if name is None:
1209 1216 if not default:
1210 1217 default = ()
1211 1218 elif not isinstance(default, (tuple, list)):
1212 1219 default = (default,)
1213 1220 for k in default:
1214 1221 try:
1215 1222 return self[k]
1216 1223 except KeyError:
1217 1224 continue
1218 1225 return None
1219 1226
1220 1227 # Most likely empty string.
1221 1228 # This may need to raise in the future.
1222 1229 if not name:
1223 1230 return None
1224 1231
1225 1232 try:
1226 1233 return self[name]
1227 1234 except KeyError:
1228 1235 # Try to resolve as a local path or URI.
1229 1236 try:
1230 1237 # We don't pass sub-options in, so no need to pass ui instance.
1231 1238 return path(None, None, rawloc=name)
1232 1239 except ValueError:
1233 1240 raise error.RepoError(_('repository %s does not exist') %
1234 1241 name)
1235 1242
1236 1243 _pathsuboptions = {}
1237 1244
1238 1245 def pathsuboption(option, attr):
1239 1246 """Decorator used to declare a path sub-option.
1240 1247
1241 1248 Arguments are the sub-option name and the attribute it should set on
1242 1249 ``path`` instances.
1243 1250
1244 1251 The decorated function will receive as arguments a ``ui`` instance,
1245 1252 ``path`` instance, and the string value of this option from the config.
1246 1253 The function should return the value that will be set on the ``path``
1247 1254 instance.
1248 1255
1249 1256 This decorator can be used to perform additional verification of
1250 1257 sub-options and to change the type of sub-options.
1251 1258 """
1252 1259 def register(func):
1253 1260 _pathsuboptions[option] = (attr, func)
1254 1261 return func
1255 1262 return register
1256 1263
1257 1264 @pathsuboption('pushurl', 'pushloc')
1258 1265 def pushurlpathoption(ui, path, value):
1259 1266 u = util.url(value)
1260 1267 # Actually require a URL.
1261 1268 if not u.scheme:
1262 1269 ui.warn(_('(paths.%s:pushurl not a URL; ignoring)\n') % path.name)
1263 1270 return None
1264 1271
1265 1272 # Don't support the #foo syntax in the push URL to declare branch to
1266 1273 # push.
1267 1274 if u.fragment:
1268 1275 ui.warn(_('("#fragment" in paths.%s:pushurl not supported; '
1269 1276 'ignoring)\n') % path.name)
1270 1277 u.fragment = None
1271 1278
1272 1279 return str(u)
1273 1280
1274 1281 class path(object):
1275 1282 """Represents an individual path and its configuration."""
1276 1283
1277 1284 def __init__(self, ui, name, rawloc=None, suboptions=None):
1278 1285 """Construct a path from its config options.
1279 1286
1280 1287 ``ui`` is the ``ui`` instance the path is coming from.
1281 1288 ``name`` is the symbolic name of the path.
1282 1289 ``rawloc`` is the raw location, as defined in the config.
1283 1290 ``pushloc`` is the raw locations pushes should be made to.
1284 1291
1285 1292 If ``name`` is not defined, we require that the location be a) a local
1286 1293 filesystem path with a .hg directory or b) a URL. If not,
1287 1294 ``ValueError`` is raised.
1288 1295 """
1289 1296 if not rawloc:
1290 1297 raise ValueError('rawloc must be defined')
1291 1298
1292 1299 # Locations may define branches via syntax <base>#<branch>.
1293 1300 u = util.url(rawloc)
1294 1301 branch = None
1295 1302 if u.fragment:
1296 1303 branch = u.fragment
1297 1304 u.fragment = None
1298 1305
1299 1306 self.url = u
1300 1307 self.branch = branch
1301 1308
1302 1309 self.name = name
1303 1310 self.rawloc = rawloc
1304 1311 self.loc = str(u)
1305 1312
1306 1313 # When given a raw location but not a symbolic name, validate the
1307 1314 # location is valid.
1308 1315 if not name and not u.scheme and not self._isvalidlocalpath(self.loc):
1309 1316 raise ValueError('location is not a URL or path to a local '
1310 1317 'repo: %s' % rawloc)
1311 1318
1312 1319 suboptions = suboptions or {}
1313 1320
1314 1321 # Now process the sub-options. If a sub-option is registered, its
1315 1322 # attribute will always be present. The value will be None if there
1316 1323 # was no valid sub-option.
1317 1324 for suboption, (attr, func) in _pathsuboptions.iteritems():
1318 1325 if suboption not in suboptions:
1319 1326 setattr(self, attr, None)
1320 1327 continue
1321 1328
1322 1329 value = func(ui, self, suboptions[suboption])
1323 1330 setattr(self, attr, value)
1324 1331
1325 1332 def _isvalidlocalpath(self, path):
1326 1333 """Returns True if the given path is a potentially valid repository.
1327 1334 This is its own function so that extensions can change the definition of
1328 1335 'valid' in this case (like when pulling from a git repo into a hg
1329 1336 one)."""
1330 1337 return os.path.isdir(os.path.join(path, '.hg'))
1331 1338
1332 1339 @property
1333 1340 def suboptions(self):
1334 1341 """Return sub-options and their values for this path.
1335 1342
1336 1343 This is intended to be used for presentation purposes.
1337 1344 """
1338 1345 d = {}
1339 1346 for subopt, (attr, _func) in _pathsuboptions.iteritems():
1340 1347 value = getattr(self, attr)
1341 1348 if value is not None:
1342 1349 d[subopt] = value
1343 1350 return d
1344 1351
1345 1352 # we instantiate one globally shared progress bar to avoid
1346 1353 # competing progress bars when multiple UI objects get created
1347 1354 _progresssingleton = None
1348 1355
1349 1356 def getprogbar(ui):
1350 1357 global _progresssingleton
1351 1358 if _progresssingleton is None:
1352 1359 # passing 'ui' object to the singleton is fishy,
1353 1360 # this is how the extension used to work but feel free to rework it.
1354 1361 _progresssingleton = progress.progbar(ui)
1355 1362 return _progresssingleton
@@ -1,520 +1,520
1 1 # url.py - HTTP 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
10 10 from __future__ import absolute_import
11 11
12 12 import base64
13 13 import httplib
14 14 import os
15 15 import socket
16 16
17 17 from .i18n import _
18 18 from . import (
19 19 error,
20 20 httpconnection as httpconnectionmod,
21 21 keepalive,
22 22 sslutil,
23 23 util,
24 24 )
25 25 stringio = util.stringio
26 26
27 27 urlerr = util.urlerr
28 28 urlreq = util.urlreq
29 29
30 30 class passwordmgr(object):
31 31 def __init__(self, ui, passwddb):
32 32 self.ui = ui
33 33 self.passwddb = passwddb
34 34
35 35 def add_password(self, realm, uri, user, passwd):
36 36 return self.passwddb.add_password(realm, uri, user, passwd)
37 37
38 38 def find_user_password(self, realm, authuri):
39 39 authinfo = self.passwddb.find_user_password(realm, authuri)
40 40 user, passwd = authinfo
41 41 if user and passwd:
42 42 self._writedebug(user, passwd)
43 43 return (user, passwd)
44 44
45 45 if not user or not passwd:
46 46 res = httpconnectionmod.readauthforuri(self.ui, authuri, user)
47 47 if res:
48 48 group, auth = res
49 49 user, passwd = auth.get('username'), auth.get('password')
50 50 self.ui.debug("using auth.%s.* for authentication\n" % group)
51 51 if not user or not passwd:
52 52 u = util.url(authuri)
53 53 u.query = None
54 54 if not self.ui.interactive():
55 55 raise error.Abort(_('http authorization required for %s') %
56 56 util.hidepassword(str(u)))
57 57
58 58 self.ui.write(_("http authorization required for %s\n") %
59 59 util.hidepassword(str(u)))
60 60 self.ui.write(_("realm: %s\n") % realm)
61 61 if user:
62 62 self.ui.write(_("user: %s\n") % user)
63 63 else:
64 64 user = self.ui.prompt(_("user:"), default=None)
65 65
66 66 if not passwd:
67 67 passwd = self.ui.getpass()
68 68
69 69 self.passwddb.add_password(realm, authuri, user, passwd)
70 70 self._writedebug(user, passwd)
71 71 return (user, passwd)
72 72
73 73 def _writedebug(self, user, passwd):
74 74 msg = _('http auth: user %s, password %s\n')
75 75 self.ui.debug(msg % (user, passwd and '*' * len(passwd) or 'not set'))
76 76
77 77 def find_stored_password(self, authuri):
78 78 return self.passwddb.find_user_password(None, authuri)
79 79
80 80 class proxyhandler(urlreq.proxyhandler):
81 81 def __init__(self, ui):
82 82 proxyurl = ui.config("http_proxy", "host") or os.getenv('http_proxy')
83 83 # XXX proxyauthinfo = None
84 84
85 85 if proxyurl:
86 86 # proxy can be proper url or host[:port]
87 87 if not (proxyurl.startswith('http:') or
88 88 proxyurl.startswith('https:')):
89 89 proxyurl = 'http://' + proxyurl + '/'
90 90 proxy = util.url(proxyurl)
91 91 if not proxy.user:
92 92 proxy.user = ui.config("http_proxy", "user")
93 93 proxy.passwd = ui.config("http_proxy", "passwd")
94 94
95 95 # see if we should use a proxy for this url
96 96 no_list = ["localhost", "127.0.0.1"]
97 97 no_list.extend([p.lower() for
98 98 p in ui.configlist("http_proxy", "no")])
99 99 no_list.extend([p.strip().lower() for
100 100 p in os.getenv("no_proxy", '').split(',')
101 101 if p.strip()])
102 102 # "http_proxy.always" config is for running tests on localhost
103 103 if ui.configbool("http_proxy", "always"):
104 104 self.no_list = []
105 105 else:
106 106 self.no_list = no_list
107 107
108 108 proxyurl = str(proxy)
109 109 proxies = {'http': proxyurl, 'https': proxyurl}
110 110 ui.debug('proxying through http://%s:%s\n' %
111 111 (proxy.host, proxy.port))
112 112 else:
113 113 proxies = {}
114 114
115 115 # urllib2 takes proxy values from the environment and those
116 116 # will take precedence if found. So, if there's a config entry
117 117 # defining a proxy, drop the environment ones
118 118 if ui.config("http_proxy", "host"):
119 119 for env in ["HTTP_PROXY", "http_proxy", "no_proxy"]:
120 120 try:
121 121 if env in os.environ:
122 122 del os.environ[env]
123 123 except OSError:
124 124 pass
125 125
126 126 urlreq.proxyhandler.__init__(self, proxies)
127 127 self.ui = ui
128 128
129 129 def proxy_open(self, req, proxy, type_):
130 130 host = req.get_host().split(':')[0]
131 131 for e in self.no_list:
132 132 if host == e:
133 133 return None
134 134 if e.startswith('*.') and host.endswith(e[2:]):
135 135 return None
136 136 if e.startswith('.') and host.endswith(e[1:]):
137 137 return None
138 138
139 139 return urlreq.proxyhandler.proxy_open(self, req, proxy, type_)
140 140
141 141 def _gen_sendfile(orgsend):
142 142 def _sendfile(self, data):
143 143 # send a file
144 144 if isinstance(data, httpconnectionmod.httpsendfile):
145 145 # if auth required, some data sent twice, so rewind here
146 146 data.seek(0)
147 147 for chunk in util.filechunkiter(data):
148 148 orgsend(self, chunk)
149 149 else:
150 150 orgsend(self, data)
151 151 return _sendfile
152 152
153 153 has_https = util.safehasattr(urlreq, 'httpshandler')
154 154 if has_https:
155 155 try:
156 156 _create_connection = socket.create_connection
157 157 except AttributeError:
158 158 _GLOBAL_DEFAULT_TIMEOUT = object()
159 159
160 160 def _create_connection(address, timeout=_GLOBAL_DEFAULT_TIMEOUT,
161 161 source_address=None):
162 162 # lifted from Python 2.6
163 163
164 164 msg = "getaddrinfo returns an empty list"
165 165 host, port = address
166 166 for res in socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM):
167 167 af, socktype, proto, canonname, sa = res
168 168 sock = None
169 169 try:
170 170 sock = socket.socket(af, socktype, proto)
171 171 if timeout is not _GLOBAL_DEFAULT_TIMEOUT:
172 172 sock.settimeout(timeout)
173 173 if source_address:
174 174 sock.bind(source_address)
175 175 sock.connect(sa)
176 176 return sock
177 177
178 178 except socket.error as msg:
179 179 if sock is not None:
180 180 sock.close()
181 181
182 182 raise socket.error(msg)
183 183
184 184 class httpconnection(keepalive.HTTPConnection):
185 185 # must be able to send big bundle as stream.
186 186 send = _gen_sendfile(keepalive.HTTPConnection.send)
187 187
188 188 def connect(self):
189 189 if has_https and self.realhostport: # use CONNECT proxy
190 190 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
191 191 self.sock.connect((self.host, self.port))
192 192 if _generic_proxytunnel(self):
193 193 # we do not support client X.509 certificates
194 194 self.sock = sslutil.wrapsocket(self.sock, None, None, None,
195 195 serverhostname=self.host)
196 196 else:
197 197 keepalive.HTTPConnection.connect(self)
198 198
199 199 def getresponse(self):
200 200 proxyres = getattr(self, 'proxyres', None)
201 201 if proxyres:
202 202 if proxyres.will_close:
203 203 self.close()
204 204 self.proxyres = None
205 205 return proxyres
206 206 return keepalive.HTTPConnection.getresponse(self)
207 207
208 208 # general transaction handler to support different ways to handle
209 209 # HTTPS proxying before and after Python 2.6.3.
210 210 def _generic_start_transaction(handler, h, req):
211 211 tunnel_host = getattr(req, '_tunnel_host', None)
212 212 if tunnel_host:
213 213 if tunnel_host[:7] not in ['http://', 'https:/']:
214 214 tunnel_host = 'https://' + tunnel_host
215 215 new_tunnel = True
216 216 else:
217 217 tunnel_host = req.get_selector()
218 218 new_tunnel = False
219 219
220 220 if new_tunnel or tunnel_host == req.get_full_url(): # has proxy
221 221 u = util.url(tunnel_host)
222 222 if new_tunnel or u.scheme == 'https': # only use CONNECT for HTTPS
223 223 h.realhostport = ':'.join([u.host, (u.port or '443')])
224 224 h.headers = req.headers.copy()
225 225 h.headers.update(handler.parent.addheaders)
226 226 return
227 227
228 228 h.realhostport = None
229 229 h.headers = None
230 230
231 231 def _generic_proxytunnel(self):
232 232 proxyheaders = dict(
233 233 [(x, self.headers[x]) for x in self.headers
234 234 if x.lower().startswith('proxy-')])
235 235 self.send('CONNECT %s HTTP/1.0\r\n' % self.realhostport)
236 236 for header in proxyheaders.iteritems():
237 237 self.send('%s: %s\r\n' % header)
238 238 self.send('\r\n')
239 239
240 240 # majority of the following code is duplicated from
241 241 # httplib.HTTPConnection as there are no adequate places to
242 242 # override functions to provide the needed functionality
243 243 res = self.response_class(self.sock,
244 244 strict=self.strict,
245 245 method=self._method)
246 246
247 247 while True:
248 248 version, status, reason = res._read_status()
249 249 if status != httplib.CONTINUE:
250 250 break
251 251 while True:
252 252 skip = res.fp.readline().strip()
253 253 if not skip:
254 254 break
255 255 res.status = status
256 256 res.reason = reason.strip()
257 257
258 258 if res.status == 200:
259 259 while True:
260 260 line = res.fp.readline()
261 261 if line == '\r\n':
262 262 break
263 263 return True
264 264
265 265 if version == 'HTTP/1.0':
266 266 res.version = 10
267 267 elif version.startswith('HTTP/1.'):
268 268 res.version = 11
269 269 elif version == 'HTTP/0.9':
270 270 res.version = 9
271 271 else:
272 272 raise httplib.UnknownProtocol(version)
273 273
274 274 if res.version == 9:
275 275 res.length = None
276 276 res.chunked = 0
277 277 res.will_close = 1
278 278 res.msg = httplib.HTTPMessage(stringio())
279 279 return False
280 280
281 281 res.msg = httplib.HTTPMessage(res.fp)
282 282 res.msg.fp = None
283 283
284 284 # are we using the chunked-style of transfer encoding?
285 285 trenc = res.msg.getheader('transfer-encoding')
286 286 if trenc and trenc.lower() == "chunked":
287 287 res.chunked = 1
288 288 res.chunk_left = None
289 289 else:
290 290 res.chunked = 0
291 291
292 292 # will the connection close at the end of the response?
293 293 res.will_close = res._check_close()
294 294
295 295 # do we have a Content-Length?
296 296 # NOTE: RFC 2616, section 4.4, #3 says we ignore this if
297 297 # transfer-encoding is "chunked"
298 298 length = res.msg.getheader('content-length')
299 299 if length and not res.chunked:
300 300 try:
301 301 res.length = int(length)
302 302 except ValueError:
303 303 res.length = None
304 304 else:
305 305 if res.length < 0: # ignore nonsensical negative lengths
306 306 res.length = None
307 307 else:
308 308 res.length = None
309 309
310 310 # does the body have a fixed length? (of zero)
311 311 if (status == httplib.NO_CONTENT or status == httplib.NOT_MODIFIED or
312 312 100 <= status < 200 or # 1xx codes
313 313 res._method == 'HEAD'):
314 314 res.length = 0
315 315
316 316 # if the connection remains open, and we aren't using chunked, and
317 317 # a content-length was not provided, then assume that the connection
318 318 # WILL close.
319 319 if (not res.will_close and
320 320 not res.chunked and
321 321 res.length is None):
322 322 res.will_close = 1
323 323
324 324 self.proxyres = res
325 325
326 326 return False
327 327
328 328 class httphandler(keepalive.HTTPHandler):
329 329 def http_open(self, req):
330 330 return self.do_open(httpconnection, req)
331 331
332 332 def _start_transaction(self, h, req):
333 333 _generic_start_transaction(self, h, req)
334 334 return keepalive.HTTPHandler._start_transaction(self, h, req)
335 335
336 336 if has_https:
337 337 class httpsconnection(httplib.HTTPConnection):
338 338 response_class = keepalive.HTTPResponse
339 339 default_port = httplib.HTTPS_PORT
340 340 # must be able to send big bundle as stream.
341 341 send = _gen_sendfile(keepalive.safesend)
342 342 getresponse = keepalive.wrapgetresponse(httplib.HTTPConnection)
343 343
344 344 def __init__(self, host, port=None, key_file=None, cert_file=None,
345 345 *args, **kwargs):
346 346 httplib.HTTPConnection.__init__(self, host, port, *args, **kwargs)
347 347 self.key_file = key_file
348 348 self.cert_file = cert_file
349 349
350 350 def connect(self):
351 351 self.sock = _create_connection((self.host, self.port))
352 352
353 353 host = self.host
354 354 if self.realhostport: # use CONNECT proxy
355 355 _generic_proxytunnel(self)
356 356 host = self.realhostport.rsplit(':', 1)[0]
357 357 self.sock = sslutil.wrapsocket(
358 358 self.sock, self.key_file, self.cert_file, ui=self.ui,
359 359 serverhostname=host)
360 360 sslutil.validatesocket(self.sock)
361 361
362 362 class httpshandler(keepalive.KeepAliveHandler, urlreq.httpshandler):
363 363 def __init__(self, ui):
364 364 keepalive.KeepAliveHandler.__init__(self)
365 365 urlreq.httpshandler.__init__(self)
366 366 self.ui = ui
367 367 self.pwmgr = passwordmgr(self.ui,
368 urlreq.httppasswordmgrwithdefaultrealm())
368 self.ui.httppasswordmgrdb)
369 369
370 370 def _start_transaction(self, h, req):
371 371 _generic_start_transaction(self, h, req)
372 372 return keepalive.KeepAliveHandler._start_transaction(self, h, req)
373 373
374 374 def https_open(self, req):
375 375 # req.get_full_url() does not contain credentials and we may
376 376 # need them to match the certificates.
377 377 url = req.get_full_url()
378 378 user, password = self.pwmgr.find_stored_password(url)
379 379 res = httpconnectionmod.readauthforuri(self.ui, url, user)
380 380 if res:
381 381 group, auth = res
382 382 self.auth = auth
383 383 self.ui.debug("using auth.%s.* for authentication\n" % group)
384 384 else:
385 385 self.auth = None
386 386 return self.do_open(self._makeconnection, req)
387 387
388 388 def _makeconnection(self, host, port=None, *args, **kwargs):
389 389 keyfile = None
390 390 certfile = None
391 391
392 392 if len(args) >= 1: # key_file
393 393 keyfile = args[0]
394 394 if len(args) >= 2: # cert_file
395 395 certfile = args[1]
396 396 args = args[2:]
397 397
398 398 # if the user has specified different key/cert files in
399 399 # hgrc, we prefer these
400 400 if self.auth and 'key' in self.auth and 'cert' in self.auth:
401 401 keyfile = self.auth['key']
402 402 certfile = self.auth['cert']
403 403
404 404 conn = httpsconnection(host, port, keyfile, certfile, *args,
405 405 **kwargs)
406 406 conn.ui = self.ui
407 407 return conn
408 408
409 409 class httpdigestauthhandler(urlreq.httpdigestauthhandler):
410 410 def __init__(self, *args, **kwargs):
411 411 urlreq.httpdigestauthhandler.__init__(self, *args, **kwargs)
412 412 self.retried_req = None
413 413
414 414 def reset_retry_count(self):
415 415 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
416 416 # forever. We disable reset_retry_count completely and reset in
417 417 # http_error_auth_reqed instead.
418 418 pass
419 419
420 420 def http_error_auth_reqed(self, auth_header, host, req, headers):
421 421 # Reset the retry counter once for each request.
422 422 if req is not self.retried_req:
423 423 self.retried_req = req
424 424 self.retried = 0
425 425 return urlreq.httpdigestauthhandler.http_error_auth_reqed(
426 426 self, auth_header, host, req, headers)
427 427
428 428 class httpbasicauthhandler(urlreq.httpbasicauthhandler):
429 429 def __init__(self, *args, **kwargs):
430 430 self.auth = None
431 431 urlreq.httpbasicauthhandler.__init__(self, *args, **kwargs)
432 432 self.retried_req = None
433 433
434 434 def http_request(self, request):
435 435 if self.auth:
436 436 request.add_unredirected_header(self.auth_header, self.auth)
437 437
438 438 return request
439 439
440 440 def https_request(self, request):
441 441 if self.auth:
442 442 request.add_unredirected_header(self.auth_header, self.auth)
443 443
444 444 return request
445 445
446 446 def reset_retry_count(self):
447 447 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
448 448 # forever. We disable reset_retry_count completely and reset in
449 449 # http_error_auth_reqed instead.
450 450 pass
451 451
452 452 def http_error_auth_reqed(self, auth_header, host, req, headers):
453 453 # Reset the retry counter once for each request.
454 454 if req is not self.retried_req:
455 455 self.retried_req = req
456 456 self.retried = 0
457 457 return urlreq.httpbasicauthhandler.http_error_auth_reqed(
458 458 self, auth_header, host, req, headers)
459 459
460 460 def retry_http_basic_auth(self, host, req, realm):
461 461 user, pw = self.passwd.find_user_password(realm, req.get_full_url())
462 462 if pw is not None:
463 463 raw = "%s:%s" % (user, pw)
464 464 auth = 'Basic %s' % base64.b64encode(raw).strip()
465 465 if req.headers.get(self.auth_header, None) == auth:
466 466 return None
467 467 self.auth = auth
468 468 req.add_unredirected_header(self.auth_header, auth)
469 469 return self.parent.open(req)
470 470 else:
471 471 return None
472 472
473 473 handlerfuncs = []
474 474
475 475 def opener(ui, authinfo=None):
476 476 '''
477 477 construct an opener suitable for urllib2
478 478 authinfo will be added to the password manager
479 479 '''
480 480 # experimental config: ui.usehttp2
481 481 if ui.configbool('ui', 'usehttp2', False):
482 482 handlers = [
483 483 httpconnectionmod.http2handler(
484 484 ui,
485 passwordmgr(ui, urlreq.httppasswordmgrwithdefaultrealm()))
485 passwordmgr(ui, ui.httppasswordmgrdb))
486 486 ]
487 487 else:
488 488 handlers = [httphandler()]
489 489 if has_https:
490 490 handlers.append(httpshandler(ui))
491 491
492 492 handlers.append(proxyhandler(ui))
493 493
494 passmgr = passwordmgr(ui, urlreq.httppasswordmgrwithdefaultrealm())
494 passmgr = passwordmgr(ui, ui.httppasswordmgrdb)
495 495 if authinfo is not None:
496 496 passmgr.add_password(*authinfo)
497 497 user, passwd = authinfo[2:4]
498 498 ui.debug('http auth: user %s, password %s\n' %
499 499 (user, passwd and '*' * len(passwd) or 'not set'))
500 500
501 501 handlers.extend((httpbasicauthhandler(passmgr),
502 502 httpdigestauthhandler(passmgr)))
503 503 handlers.extend([h(ui, passmgr) for h in handlerfuncs])
504 504 opener = urlreq.buildopener(*handlers)
505 505
506 506 # 1.0 here is the _protocol_ version
507 507 opener.addheaders = [('User-agent', 'mercurial/proto-1.0')]
508 508 opener.addheaders.append(('Accept', 'application/mercurial-0.1'))
509 509 return opener
510 510
511 511 def open(ui, url_, data=None):
512 512 u = util.url(url_)
513 513 if u.scheme:
514 514 u.scheme = u.scheme.lower()
515 515 url_, authinfo = u.authinfo()
516 516 else:
517 517 path = util.normpath(os.path.abspath(url_))
518 518 url_ = 'file://' + urlreq.pathname2url(path)
519 519 authinfo = None
520 520 return opener(ui, authinfo).open(url_, data)
General Comments 0
You need to be logged in to leave comments. Login now