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