##// END OF EJS Templates
ui.paths: expand paths directly in fixconfig (issue2373)...
Benoit Boissinot -
r12662:7285b282 default
parent child Browse files
Show More
@@ -1,613 +1,612 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 i18n import _
9 9 import errno, getpass, os, socket, sys, tempfile, traceback
10 10 import config, util, error
11 11
12 12 class ui(object):
13 13 def __init__(self, src=None):
14 14 self._buffers = []
15 15 self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
16 16 self._reportuntrusted = True
17 17 self._ocfg = config.config() # overlay
18 18 self._tcfg = config.config() # trusted
19 19 self._ucfg = config.config() # untrusted
20 20 self._trustusers = set()
21 21 self._trustgroups = set()
22 22
23 23 if src:
24 24 self._tcfg = src._tcfg.copy()
25 25 self._ucfg = src._ucfg.copy()
26 26 self._ocfg = src._ocfg.copy()
27 27 self._trustusers = src._trustusers.copy()
28 28 self._trustgroups = src._trustgroups.copy()
29 29 self.environ = src.environ
30 30 self.fixconfig()
31 31 else:
32 32 # shared read-only environment
33 33 self.environ = os.environ
34 34 # we always trust global config files
35 35 for f in util.rcpath():
36 36 self.readconfig(f, trust=True)
37 37
38 38 def copy(self):
39 39 return self.__class__(self)
40 40
41 41 def _is_trusted(self, fp, f):
42 42 st = util.fstat(fp)
43 43 if util.isowner(st):
44 44 return True
45 45
46 46 tusers, tgroups = self._trustusers, self._trustgroups
47 47 if '*' in tusers or '*' in tgroups:
48 48 return True
49 49
50 50 user = util.username(st.st_uid)
51 51 group = util.groupname(st.st_gid)
52 52 if user in tusers or group in tgroups or user == util.username():
53 53 return True
54 54
55 55 if self._reportuntrusted:
56 56 self.warn(_('Not trusting file %s from untrusted '
57 57 'user %s, group %s\n') % (f, user, group))
58 58 return False
59 59
60 60 def readconfig(self, filename, root=None, trust=False,
61 61 sections=None, remap=None):
62 62 try:
63 63 fp = open(filename)
64 64 except IOError:
65 65 if not sections: # ignore unless we were looking for something
66 66 return
67 67 raise
68 68
69 69 cfg = config.config()
70 70 trusted = sections or trust or self._is_trusted(fp, filename)
71 71
72 72 try:
73 73 cfg.read(filename, fp, sections=sections, remap=remap)
74 74 except error.ConfigError, inst:
75 75 if trusted:
76 76 raise
77 77 self.warn(_("Ignored: %s\n") % str(inst))
78 78
79 79 if self.plain():
80 80 for k in ('debug', 'fallbackencoding', 'quiet', 'slash',
81 81 'logtemplate', 'style',
82 82 'traceback', 'verbose'):
83 83 if k in cfg['ui']:
84 84 del cfg['ui'][k]
85 85 for k, v in cfg.items('alias'):
86 86 del cfg['alias'][k]
87 87 for k, v in cfg.items('defaults'):
88 88 del cfg['defaults'][k]
89 89
90 90 if trusted:
91 91 self._tcfg.update(cfg)
92 92 self._tcfg.update(self._ocfg)
93 93 self._ucfg.update(cfg)
94 94 self._ucfg.update(self._ocfg)
95 95
96 96 if root is None:
97 97 root = os.path.expanduser('~')
98 98 self.fixconfig(root=root)
99 99
100 100 def fixconfig(self, root=None):
101 # expand vars and ~
101 102 # translate paths relative to root (or home) into absolute paths
102 103 root = root or os.getcwd()
103 104 for c in self._tcfg, self._ucfg, self._ocfg:
104 105 for n, p in c.items('paths'):
105 if p and "://" not in p and not os.path.isabs(p):
106 c.set("paths", n, os.path.normpath(os.path.join(root, p)))
106 if not p:
107 continue
108 if '%%' in p:
109 self.warn(_("(deprecated '%%' in path %s=%s from %s)\n")
110 % (n, p, self.configsource('paths', n)))
111 p = p.replace('%%', '%')
112 p = util.expandpath(p)
113 if '://' not in p and not os.path.isabs(p):
114 p = os.path.normpath(os.path.join(root, p))
115 c.set("paths", n, p)
107 116
108 117 # update ui options
109 118 self.debugflag = self.configbool('ui', 'debug')
110 119 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
111 120 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
112 121 if self.verbose and self.quiet:
113 122 self.quiet = self.verbose = False
114 123 self._reportuntrusted = self.configbool("ui", "report_untrusted", True)
115 124 self.tracebackflag = self.configbool('ui', 'traceback', False)
116 125
117 126 # update trust information
118 127 self._trustusers.update(self.configlist('trusted', 'users'))
119 128 self._trustgroups.update(self.configlist('trusted', 'groups'))
120 129
121 130 def setconfig(self, section, name, value, overlay=True):
122 131 if overlay:
123 132 self._ocfg.set(section, name, value)
124 133 self._tcfg.set(section, name, value)
125 134 self._ucfg.set(section, name, value)
126 135 self.fixconfig()
127 136
128 137 def _data(self, untrusted):
129 138 return untrusted and self._ucfg or self._tcfg
130 139
131 140 def configsource(self, section, name, untrusted=False):
132 141 return self._data(untrusted).source(section, name) or 'none'
133 142
134 143 def config(self, section, name, default=None, untrusted=False):
135 144 value = self._data(untrusted).get(section, name, default)
136 145 if self.debugflag and not untrusted and self._reportuntrusted:
137 146 uvalue = self._ucfg.get(section, name)
138 147 if uvalue is not None and uvalue != value:
139 148 self.debug(_("ignoring untrusted configuration option "
140 149 "%s.%s = %s\n") % (section, name, uvalue))
141 150 return value
142 151
143 152 def configbool(self, section, name, default=False, untrusted=False):
144 153 v = self.config(section, name, None, untrusted)
145 154 if v is None:
146 155 return default
147 156 if isinstance(v, bool):
148 157 return v
149 158 b = util.parsebool(v)
150 159 if b is None:
151 160 raise error.ConfigError(_("%s.%s not a boolean ('%s')")
152 161 % (section, name, v))
153 162 return b
154 163
155 164 def configlist(self, section, name, default=None, untrusted=False):
156 165 """Return a list of comma/space separated strings"""
157 166
158 167 def _parse_plain(parts, s, offset):
159 168 whitespace = False
160 169 while offset < len(s) and (s[offset].isspace() or s[offset] == ','):
161 170 whitespace = True
162 171 offset += 1
163 172 if offset >= len(s):
164 173 return None, parts, offset
165 174 if whitespace:
166 175 parts.append('')
167 176 if s[offset] == '"' and not parts[-1]:
168 177 return _parse_quote, parts, offset + 1
169 178 elif s[offset] == '"' and parts[-1][-1] == '\\':
170 179 parts[-1] = parts[-1][:-1] + s[offset]
171 180 return _parse_plain, parts, offset + 1
172 181 parts[-1] += s[offset]
173 182 return _parse_plain, parts, offset + 1
174 183
175 184 def _parse_quote(parts, s, offset):
176 185 if offset < len(s) and s[offset] == '"': # ""
177 186 parts.append('')
178 187 offset += 1
179 188 while offset < len(s) and (s[offset].isspace() or
180 189 s[offset] == ','):
181 190 offset += 1
182 191 return _parse_plain, parts, offset
183 192
184 193 while offset < len(s) and s[offset] != '"':
185 194 if (s[offset] == '\\' and offset + 1 < len(s)
186 195 and s[offset + 1] == '"'):
187 196 offset += 1
188 197 parts[-1] += '"'
189 198 else:
190 199 parts[-1] += s[offset]
191 200 offset += 1
192 201
193 202 if offset >= len(s):
194 203 real_parts = _configlist(parts[-1])
195 204 if not real_parts:
196 205 parts[-1] = '"'
197 206 else:
198 207 real_parts[0] = '"' + real_parts[0]
199 208 parts = parts[:-1]
200 209 parts.extend(real_parts)
201 210 return None, parts, offset
202 211
203 212 offset += 1
204 213 while offset < len(s) and s[offset] in [' ', ',']:
205 214 offset += 1
206 215
207 216 if offset < len(s):
208 217 if offset + 1 == len(s) and s[offset] == '"':
209 218 parts[-1] += '"'
210 219 offset += 1
211 220 else:
212 221 parts.append('')
213 222 else:
214 223 return None, parts, offset
215 224
216 225 return _parse_plain, parts, offset
217 226
218 227 def _configlist(s):
219 228 s = s.rstrip(' ,')
220 229 if not s:
221 230 return []
222 231 parser, parts, offset = _parse_plain, [''], 0
223 232 while parser:
224 233 parser, parts, offset = parser(parts, s, offset)
225 234 return parts
226 235
227 236 result = self.config(section, name, untrusted=untrusted)
228 237 if result is None:
229 238 result = default or []
230 239 if isinstance(result, basestring):
231 240 result = _configlist(result.lstrip(' ,\n'))
232 241 if result is None:
233 242 result = default or []
234 243 return result
235 244
236 245 def has_section(self, section, untrusted=False):
237 246 '''tell whether section exists in config.'''
238 247 return section in self._data(untrusted)
239 248
240 249 def configitems(self, section, untrusted=False):
241 250 items = self._data(untrusted).items(section)
242 251 if self.debugflag and not untrusted and self._reportuntrusted:
243 252 for k, v in self._ucfg.items(section):
244 253 if self._tcfg.get(section, k) != v:
245 254 self.debug(_("ignoring untrusted configuration option "
246 255 "%s.%s = %s\n") % (section, k, v))
247 256 return items
248 257
249 258 def walkconfig(self, untrusted=False):
250 259 cfg = self._data(untrusted)
251 260 for section in cfg.sections():
252 261 for name, value in self.configitems(section, untrusted):
253 262 yield section, name, str(value).replace('\n', '\\n')
254 263
255 264 def plain(self):
256 265 '''is plain mode active?
257 266
258 267 Plain mode means that all configuration variables which affect the
259 268 behavior and output of Mercurial should be ignored. Additionally, the
260 269 output should be stable, reproducible and suitable for use in scripts or
261 270 applications.
262 271
263 272 The only way to trigger plain mode is by setting the `HGPLAIN'
264 273 environment variable.
265 274 '''
266 275 return 'HGPLAIN' in os.environ
267 276
268 277 def username(self):
269 278 """Return default username to be used in commits.
270 279
271 280 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
272 281 and stop searching if one of these is set.
273 282 If not found and ui.askusername is True, ask the user, else use
274 283 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
275 284 """
276 285 user = os.environ.get("HGUSER")
277 286 if user is None:
278 287 user = self.config("ui", "username")
279 288 if user is not None:
280 289 user = os.path.expandvars(user)
281 290 if user is None:
282 291 user = os.environ.get("EMAIL")
283 292 if user is None and self.configbool("ui", "askusername"):
284 293 user = self.prompt(_("enter a commit username:"), default=None)
285 294 if user is None and not self.interactive():
286 295 try:
287 296 user = '%s@%s' % (util.getuser(), socket.getfqdn())
288 297 self.warn(_("No username found, using '%s' instead\n") % user)
289 298 except KeyError:
290 299 pass
291 300 if not user:
292 301 raise util.Abort(_('no username supplied (see "hg help config")'))
293 302 if "\n" in user:
294 303 raise util.Abort(_("username %s contains a newline\n") % repr(user))
295 304 return user
296 305
297 306 def shortuser(self, user):
298 307 """Return a short representation of a user name or email address."""
299 308 if not self.verbose:
300 309 user = util.shortuser(user)
301 310 return user
302 311
303 def _path(self, loc):
304 p = self.config('paths', loc)
305 if p:
306 if '%%' in p:
307 self.warn(_("(deprecated '%%' in path %s=%s from %s)\n") %
308 (loc, p, self.configsource('paths', loc)))
309 p = p.replace('%%', '%')
310 p = util.expandpath(p)
311 return p
312
313 312 def expandpath(self, loc, default=None):
314 313 """Return repository location relative to cwd or from [paths]"""
315 314 if "://" in loc or os.path.isdir(os.path.join(loc, '.hg')):
316 315 return loc
317 316
318 path = self._path(loc)
317 path = self.config('paths', loc)
319 318 if not path and default is not None:
320 path = self._path(default)
319 path = self.config('paths', default)
321 320 return path or loc
322 321
323 322 def pushbuffer(self):
324 323 self._buffers.append([])
325 324
326 325 def popbuffer(self, labeled=False):
327 326 '''pop the last buffer and return the buffered output
328 327
329 328 If labeled is True, any labels associated with buffered
330 329 output will be handled. By default, this has no effect
331 330 on the output returned, but extensions and GUI tools may
332 331 handle this argument and returned styled output. If output
333 332 is being buffered so it can be captured and parsed or
334 333 processed, labeled should not be set to True.
335 334 '''
336 335 return "".join(self._buffers.pop())
337 336
338 337 def write(self, *args, **opts):
339 338 '''write args to output
340 339
341 340 By default, this method simply writes to the buffer or stdout,
342 341 but extensions or GUI tools may override this method,
343 342 write_err(), popbuffer(), and label() to style output from
344 343 various parts of hg.
345 344
346 345 An optional keyword argument, "label", can be passed in.
347 346 This should be a string containing label names separated by
348 347 space. Label names take the form of "topic.type". For example,
349 348 ui.debug() issues a label of "ui.debug".
350 349
351 350 When labeling output for a specific command, a label of
352 351 "cmdname.type" is recommended. For example, status issues
353 352 a label of "status.modified" for modified files.
354 353 '''
355 354 if self._buffers:
356 355 self._buffers[-1].extend([str(a) for a in args])
357 356 else:
358 357 for a in args:
359 358 sys.stdout.write(str(a))
360 359
361 360 def write_err(self, *args, **opts):
362 361 try:
363 362 if not getattr(sys.stdout, 'closed', False):
364 363 sys.stdout.flush()
365 364 for a in args:
366 365 sys.stderr.write(str(a))
367 366 # stderr may be buffered under win32 when redirected to files,
368 367 # including stdout.
369 368 if not getattr(sys.stderr, 'closed', False):
370 369 sys.stderr.flush()
371 370 except IOError, inst:
372 371 if inst.errno not in (errno.EPIPE, errno.EIO):
373 372 raise
374 373
375 374 def flush(self):
376 375 try: sys.stdout.flush()
377 376 except: pass
378 377 try: sys.stderr.flush()
379 378 except: pass
380 379
381 380 def interactive(self):
382 381 '''is interactive input allowed?
383 382
384 383 An interactive session is a session where input can be reasonably read
385 384 from `sys.stdin'. If this function returns false, any attempt to read
386 385 from stdin should fail with an error, unless a sensible default has been
387 386 specified.
388 387
389 388 Interactiveness is triggered by the value of the `ui.interactive'
390 389 configuration variable or - if it is unset - when `sys.stdin' points
391 390 to a terminal device.
392 391
393 392 This function refers to input only; for output, see `ui.formatted()'.
394 393 '''
395 394 i = self.configbool("ui", "interactive", None)
396 395 if i is None:
397 396 try:
398 397 return sys.stdin.isatty()
399 398 except AttributeError:
400 399 # some environments replace stdin without implementing isatty
401 400 # usually those are non-interactive
402 401 return False
403 402
404 403 return i
405 404
406 405 def formatted(self):
407 406 '''should formatted output be used?
408 407
409 408 It is often desirable to format the output to suite the output medium.
410 409 Examples of this are truncating long lines or colorizing messages.
411 410 However, this is not often not desirable when piping output into other
412 411 utilities, e.g. `grep'.
413 412
414 413 Formatted output is triggered by the value of the `ui.formatted'
415 414 configuration variable or - if it is unset - when `sys.stdout' points
416 415 to a terminal device. Please note that `ui.formatted' should be
417 416 considered an implementation detail; it is not intended for use outside
418 417 Mercurial or its extensions.
419 418
420 419 This function refers to output only; for input, see `ui.interactive()'.
421 420 This function always returns false when in plain mode, see `ui.plain()'.
422 421 '''
423 422 if self.plain():
424 423 return False
425 424
426 425 i = self.configbool("ui", "formatted", None)
427 426 if i is None:
428 427 try:
429 428 return sys.stdout.isatty()
430 429 except AttributeError:
431 430 # some environments replace stdout without implementing isatty
432 431 # usually those are non-interactive
433 432 return False
434 433
435 434 return i
436 435
437 436 def _readline(self, prompt=''):
438 437 if sys.stdin.isatty():
439 438 try:
440 439 # magically add command line editing support, where
441 440 # available
442 441 import readline
443 442 # force demandimport to really load the module
444 443 readline.read_history_file
445 444 # windows sometimes raises something other than ImportError
446 445 except Exception:
447 446 pass
448 447 line = raw_input(prompt)
449 448 # When stdin is in binary mode on Windows, it can cause
450 449 # raw_input() to emit an extra trailing carriage return
451 450 if os.linesep == '\r\n' and line and line[-1] == '\r':
452 451 line = line[:-1]
453 452 return line
454 453
455 454 def prompt(self, msg, default="y"):
456 455 """Prompt user with msg, read response.
457 456 If ui is not interactive, the default is returned.
458 457 """
459 458 if not self.interactive():
460 459 self.write(msg, ' ', default, "\n")
461 460 return default
462 461 try:
463 462 r = self._readline(msg + ' ')
464 463 if not r:
465 464 return default
466 465 return r
467 466 except EOFError:
468 467 raise util.Abort(_('response expected'))
469 468
470 469 def promptchoice(self, msg, choices, default=0):
471 470 """Prompt user with msg, read response, and ensure it matches
472 471 one of the provided choices. The index of the choice is returned.
473 472 choices is a sequence of acceptable responses with the format:
474 473 ('&None', 'E&xec', 'Sym&link') Responses are case insensitive.
475 474 If ui is not interactive, the default is returned.
476 475 """
477 476 resps = [s[s.index('&')+1].lower() for s in choices]
478 477 while True:
479 478 r = self.prompt(msg, resps[default])
480 479 if r.lower() in resps:
481 480 return resps.index(r.lower())
482 481 self.write(_("unrecognized response\n"))
483 482
484 483 def getpass(self, prompt=None, default=None):
485 484 if not self.interactive():
486 485 return default
487 486 try:
488 487 return getpass.getpass(prompt or _('password: '))
489 488 except EOFError:
490 489 raise util.Abort(_('response expected'))
491 490 def status(self, *msg, **opts):
492 491 '''write status message to output (if ui.quiet is False)
493 492
494 493 This adds an output label of "ui.status".
495 494 '''
496 495 if not self.quiet:
497 496 opts['label'] = opts.get('label', '') + ' ui.status'
498 497 self.write(*msg, **opts)
499 498 def warn(self, *msg, **opts):
500 499 '''write warning message to output (stderr)
501 500
502 501 This adds an output label of "ui.warning".
503 502 '''
504 503 opts['label'] = opts.get('label', '') + ' ui.warning'
505 504 self.write_err(*msg, **opts)
506 505 def note(self, *msg, **opts):
507 506 '''write note to output (if ui.verbose is True)
508 507
509 508 This adds an output label of "ui.note".
510 509 '''
511 510 if self.verbose:
512 511 opts['label'] = opts.get('label', '') + ' ui.note'
513 512 self.write(*msg, **opts)
514 513 def debug(self, *msg, **opts):
515 514 '''write debug message to output (if ui.debugflag is True)
516 515
517 516 This adds an output label of "ui.debug".
518 517 '''
519 518 if self.debugflag:
520 519 opts['label'] = opts.get('label', '') + ' ui.debug'
521 520 self.write(*msg, **opts)
522 521 def edit(self, text, user):
523 522 (fd, name) = tempfile.mkstemp(prefix="hg-editor-", suffix=".txt",
524 523 text=True)
525 524 try:
526 525 f = os.fdopen(fd, "w")
527 526 f.write(text)
528 527 f.close()
529 528
530 529 editor = self.geteditor()
531 530
532 531 util.system("%s \"%s\"" % (editor, name),
533 532 environ={'HGUSER': user},
534 533 onerr=util.Abort, errprefix=_("edit failed"))
535 534
536 535 f = open(name)
537 536 t = f.read()
538 537 f.close()
539 538 finally:
540 539 os.unlink(name)
541 540
542 541 return t
543 542
544 543 def traceback(self, exc=None):
545 544 '''print exception traceback if traceback printing enabled.
546 545 only to call in exception handler. returns true if traceback
547 546 printed.'''
548 547 if self.tracebackflag:
549 548 if exc:
550 549 traceback.print_exception(exc[0], exc[1], exc[2])
551 550 else:
552 551 traceback.print_exc()
553 552 return self.tracebackflag
554 553
555 554 def geteditor(self):
556 555 '''return editor to use'''
557 556 return (os.environ.get("HGEDITOR") or
558 557 self.config("ui", "editor") or
559 558 os.environ.get("VISUAL") or
560 559 os.environ.get("EDITOR", "vi"))
561 560
562 561 def progress(self, topic, pos, item="", unit="", total=None):
563 562 '''show a progress message
564 563
565 564 With stock hg, this is simply a debug message that is hidden
566 565 by default, but with extensions or GUI tools it may be
567 566 visible. 'topic' is the current operation, 'item' is a
568 567 non-numeric marker of the current position (ie the currently
569 568 in-process file), 'pos' is the current numeric position (ie
570 569 revision, bytes, etc.), unit is a corresponding unit label,
571 570 and total is the highest expected pos.
572 571
573 572 Multiple nested topics may be active at a time.
574 573
575 574 All topics should be marked closed by setting pos to None at
576 575 termination.
577 576 '''
578 577
579 578 if pos == None or not self.debugflag:
580 579 return
581 580
582 581 if unit:
583 582 unit = ' ' + unit
584 583 if item:
585 584 item = ' ' + item
586 585
587 586 if total:
588 587 pct = 100.0 * pos / total
589 588 self.debug('%s:%s %s/%s%s (%4.2f%%)\n'
590 589 % (topic, item, pos, total, unit, pct))
591 590 else:
592 591 self.debug('%s:%s %s%s\n' % (topic, item, pos, unit))
593 592
594 593 def log(self, service, message):
595 594 '''hook for logging facility extensions
596 595
597 596 service should be a readily-identifiable subsystem, which will
598 597 allow filtering.
599 598 message should be a newline-terminated string to log.
600 599 '''
601 600 pass
602 601
603 602 def label(self, msg, label):
604 603 '''style msg based on supplied label
605 604
606 605 Like ui.write(), this just returns msg unchanged, but extensions
607 606 and GUI tools can override it to allow styling output without
608 607 writing it.
609 608
610 609 ui.write(s, 'label') is equivalent to
611 610 ui.write(ui.label(s, 'label')).
612 611 '''
613 612 return msg
@@ -1,16 +1,27 b''
1 1 $ hg init a
2 2 $ hg clone a b
3 3 updating to branch default
4 4 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
5 5 $ cd a
6 6 $ echo '[paths]' >> .hg/hgrc
7 7 $ echo 'dupe = ../b' >> .hg/hgrc
8 $ echo 'expand = $SOMETHING/bar' >> .hg/hgrc
8 9 $ hg in dupe
9 10 comparing with $TESTTMP/b
10 11 no changes found
11 12 [1]
12 13 $ cd ..
13 14 $ hg -R a in dupe
14 15 comparing with $TESTTMP/b
15 16 no changes found
16 17 [1]
18 $ cd a
19 $ hg paths
20 dupe = $TESTTMP/b
21 expand = $TESTTMP/a/$SOMETHING/bar
22 $ SOMETHING=foo hg paths
23 dupe = $TESTTMP/b
24 expand = $TESTTMP/a/foo/bar
25 $ SOMETHING=/foo hg paths
26 dupe = $TESTTMP/b
27 expand = /foo/bar
General Comments 0
You need to be logged in to leave comments. Login now