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