##// END OF EJS Templates
dispatch: making all hg abortions be output with a specific label...
Rodrigo Damazio Bovendorp -
r38791:afc4ad70 default
parent child Browse files
Show More
@@ -1,534 +1,535 b''
1 1 # utility for color output for Mercurial commands
2 2 #
3 3 # Copyright (C) 2007 Kevin Christen <kevin.christen@gmail.com> and other
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 re
11 11
12 12 from .i18n import _
13 13
14 14 from . import (
15 15 encoding,
16 16 pycompat,
17 17 )
18 18
19 19 from .utils import (
20 20 stringutil,
21 21 )
22 22
23 23 try:
24 24 import curses
25 25 # Mapping from effect name to terminfo attribute name (or raw code) or
26 26 # color number. This will also force-load the curses module.
27 27 _baseterminfoparams = {
28 28 'none': (True, 'sgr0', ''),
29 29 'standout': (True, 'smso', ''),
30 30 'underline': (True, 'smul', ''),
31 31 'reverse': (True, 'rev', ''),
32 32 'inverse': (True, 'rev', ''),
33 33 'blink': (True, 'blink', ''),
34 34 'dim': (True, 'dim', ''),
35 35 'bold': (True, 'bold', ''),
36 36 'invisible': (True, 'invis', ''),
37 37 'italic': (True, 'sitm', ''),
38 38 'black': (False, curses.COLOR_BLACK, ''),
39 39 'red': (False, curses.COLOR_RED, ''),
40 40 'green': (False, curses.COLOR_GREEN, ''),
41 41 'yellow': (False, curses.COLOR_YELLOW, ''),
42 42 'blue': (False, curses.COLOR_BLUE, ''),
43 43 'magenta': (False, curses.COLOR_MAGENTA, ''),
44 44 'cyan': (False, curses.COLOR_CYAN, ''),
45 45 'white': (False, curses.COLOR_WHITE, ''),
46 46 }
47 47 except ImportError:
48 48 curses = None
49 49 _baseterminfoparams = {}
50 50
51 51 # start and stop parameters for effects
52 52 _effects = {
53 53 'none': 0,
54 54 'black': 30,
55 55 'red': 31,
56 56 'green': 32,
57 57 'yellow': 33,
58 58 'blue': 34,
59 59 'magenta': 35,
60 60 'cyan': 36,
61 61 'white': 37,
62 62 'bold': 1,
63 63 'italic': 3,
64 64 'underline': 4,
65 65 'inverse': 7,
66 66 'dim': 2,
67 67 'black_background': 40,
68 68 'red_background': 41,
69 69 'green_background': 42,
70 70 'yellow_background': 43,
71 71 'blue_background': 44,
72 72 'purple_background': 45,
73 73 'cyan_background': 46,
74 74 'white_background': 47,
75 75 }
76 76
77 77 _defaultstyles = {
78 78 'grep.match': 'red bold',
79 79 'grep.linenumber': 'green',
80 80 'grep.rev': 'green',
81 81 'grep.change': 'green',
82 82 'grep.sep': 'cyan',
83 83 'grep.filename': 'magenta',
84 84 'grep.user': 'magenta',
85 85 'grep.date': 'magenta',
86 86 'bookmarks.active': 'green',
87 87 'branches.active': 'none',
88 88 'branches.closed': 'black bold',
89 89 'branches.current': 'green',
90 90 'branches.inactive': 'none',
91 91 'diff.changed': 'white',
92 92 'diff.deleted': 'red',
93 93 'diff.deleted.changed': 'red bold underline',
94 94 'diff.deleted.unchanged': 'red',
95 95 'diff.diffline': 'bold',
96 96 'diff.extended': 'cyan bold',
97 97 'diff.file_a': 'red bold',
98 98 'diff.file_b': 'green bold',
99 99 'diff.hunk': 'magenta',
100 100 'diff.inserted': 'green',
101 101 'diff.inserted.changed': 'green bold underline',
102 102 'diff.inserted.unchanged': 'green',
103 103 'diff.tab': '',
104 104 'diff.trailingwhitespace': 'bold red_background',
105 105 'changeset.public': '',
106 106 'changeset.draft': '',
107 107 'changeset.secret': '',
108 108 'diffstat.deleted': 'red',
109 109 'diffstat.inserted': 'green',
110 110 'formatvariant.name.mismatchconfig': 'red',
111 111 'formatvariant.name.mismatchdefault': 'yellow',
112 112 'formatvariant.name.uptodate': 'green',
113 113 'formatvariant.repo.mismatchconfig': 'red',
114 114 'formatvariant.repo.mismatchdefault': 'yellow',
115 115 'formatvariant.repo.uptodate': 'green',
116 116 'formatvariant.config.special': 'yellow',
117 117 'formatvariant.config.default': 'green',
118 118 'formatvariant.default': '',
119 119 'histedit.remaining': 'red bold',
120 'ui.error': 'red',
120 121 'ui.prompt': 'yellow',
121 122 'log.changeset': 'yellow',
122 123 'patchbomb.finalsummary': '',
123 124 'patchbomb.from': 'magenta',
124 125 'patchbomb.to': 'cyan',
125 126 'patchbomb.subject': 'green',
126 127 'patchbomb.diffstats': '',
127 128 'rebase.rebased': 'blue',
128 129 'rebase.remaining': 'red bold',
129 130 'resolve.resolved': 'green bold',
130 131 'resolve.unresolved': 'red bold',
131 132 'shelve.age': 'cyan',
132 133 'shelve.newest': 'green bold',
133 134 'shelve.name': 'blue bold',
134 135 'status.added': 'green bold',
135 136 'status.clean': 'none',
136 137 'status.copied': 'none',
137 138 'status.deleted': 'cyan bold underline',
138 139 'status.ignored': 'black bold',
139 140 'status.modified': 'blue bold',
140 141 'status.removed': 'red bold',
141 142 'status.unknown': 'magenta bold underline',
142 143 'tags.normal': 'green',
143 144 'tags.local': 'black bold',
144 145 }
145 146
146 147 def loadcolortable(ui, extname, colortable):
147 148 _defaultstyles.update(colortable)
148 149
149 150 def _terminfosetup(ui, mode, formatted):
150 151 '''Initialize terminfo data and the terminal if we're in terminfo mode.'''
151 152
152 153 # If we failed to load curses, we go ahead and return.
153 154 if curses is None:
154 155 return
155 156 # Otherwise, see what the config file says.
156 157 if mode not in ('auto', 'terminfo'):
157 158 return
158 159 ui._terminfoparams.update(_baseterminfoparams)
159 160
160 161 for key, val in ui.configitems('color'):
161 162 if key.startswith('color.'):
162 163 newval = (False, int(val), '')
163 164 ui._terminfoparams[key[6:]] = newval
164 165 elif key.startswith('terminfo.'):
165 166 newval = (True, '', val.replace('\\E', '\x1b'))
166 167 ui._terminfoparams[key[9:]] = newval
167 168 try:
168 169 curses.setupterm()
169 170 except curses.error as e:
170 171 ui._terminfoparams.clear()
171 172 return
172 173
173 174 for key, (b, e, c) in ui._terminfoparams.copy().items():
174 175 if not b:
175 176 continue
176 177 if not c and not curses.tigetstr(pycompat.sysstr(e)):
177 178 # Most terminals don't support dim, invis, etc, so don't be
178 179 # noisy and use ui.debug().
179 180 ui.debug("no terminfo entry for %s\n" % e)
180 181 del ui._terminfoparams[key]
181 182 if not curses.tigetstr(r'setaf') or not curses.tigetstr(r'setab'):
182 183 # Only warn about missing terminfo entries if we explicitly asked for
183 184 # terminfo mode and we're in a formatted terminal.
184 185 if mode == "terminfo" and formatted:
185 186 ui.warn(_("no terminfo entry for setab/setaf: reverting to "
186 187 "ECMA-48 color\n"))
187 188 ui._terminfoparams.clear()
188 189
189 190 def setup(ui):
190 191 """configure color on a ui
191 192
192 193 That function both set the colormode for the ui object and read
193 194 the configuration looking for custom colors and effect definitions."""
194 195 mode = _modesetup(ui)
195 196 ui._colormode = mode
196 197 if mode and mode != 'debug':
197 198 configstyles(ui)
198 199
199 200 def _modesetup(ui):
200 201 if ui.plain('color'):
201 202 return None
202 203 config = ui.config('ui', 'color')
203 204 if config == 'debug':
204 205 return 'debug'
205 206
206 207 auto = (config == 'auto')
207 208 always = False
208 209 if not auto and stringutil.parsebool(config):
209 210 # We want the config to behave like a boolean, "on" is actually auto,
210 211 # but "always" value is treated as a special case to reduce confusion.
211 212 if ui.configsource('ui', 'color') == '--color' or config == 'always':
212 213 always = True
213 214 else:
214 215 auto = True
215 216
216 217 if not always and not auto:
217 218 return None
218 219
219 220 formatted = (always or (encoding.environ.get('TERM') != 'dumb'
220 221 and ui.formatted()))
221 222
222 223 mode = ui.config('color', 'mode')
223 224
224 225 # If pager is active, color.pagermode overrides color.mode.
225 226 if getattr(ui, 'pageractive', False):
226 227 mode = ui.config('color', 'pagermode', mode)
227 228
228 229 realmode = mode
229 230 if pycompat.iswindows:
230 231 from . import win32
231 232
232 233 term = encoding.environ.get('TERM')
233 234 # TERM won't be defined in a vanilla cmd.exe environment.
234 235
235 236 # UNIX-like environments on Windows such as Cygwin and MSYS will
236 237 # set TERM. They appear to make a best effort attempt at setting it
237 238 # to something appropriate. However, not all environments with TERM
238 239 # defined support ANSI.
239 240 ansienviron = term and 'xterm' in term
240 241
241 242 if mode == 'auto':
242 243 # Since "ansi" could result in terminal gibberish, we error on the
243 244 # side of selecting "win32". However, if w32effects is not defined,
244 245 # we almost certainly don't support "win32", so don't even try.
245 246 # w32ffects is not populated when stdout is redirected, so checking
246 247 # it first avoids win32 calls in a state known to error out.
247 248 if ansienviron or not w32effects or win32.enablevtmode():
248 249 realmode = 'ansi'
249 250 else:
250 251 realmode = 'win32'
251 252 # An empty w32effects is a clue that stdout is redirected, and thus
252 253 # cannot enable VT mode.
253 254 elif mode == 'ansi' and w32effects and not ansienviron:
254 255 win32.enablevtmode()
255 256 elif mode == 'auto':
256 257 realmode = 'ansi'
257 258
258 259 def modewarn():
259 260 # only warn if color.mode was explicitly set and we're in
260 261 # a formatted terminal
261 262 if mode == realmode and formatted:
262 263 ui.warn(_('warning: failed to set color mode to %s\n') % mode)
263 264
264 265 if realmode == 'win32':
265 266 ui._terminfoparams.clear()
266 267 if not w32effects:
267 268 modewarn()
268 269 return None
269 270 elif realmode == 'ansi':
270 271 ui._terminfoparams.clear()
271 272 elif realmode == 'terminfo':
272 273 _terminfosetup(ui, mode, formatted)
273 274 if not ui._terminfoparams:
274 275 ## FIXME Shouldn't we return None in this case too?
275 276 modewarn()
276 277 realmode = 'ansi'
277 278 else:
278 279 return None
279 280
280 281 if always or (auto and formatted):
281 282 return realmode
282 283 return None
283 284
284 285 def configstyles(ui):
285 286 ui._styles.update(_defaultstyles)
286 287 for status, cfgeffects in ui.configitems('color'):
287 288 if '.' not in status or status.startswith(('color.', 'terminfo.')):
288 289 continue
289 290 cfgeffects = ui.configlist('color', status)
290 291 if cfgeffects:
291 292 good = []
292 293 for e in cfgeffects:
293 294 if valideffect(ui, e):
294 295 good.append(e)
295 296 else:
296 297 ui.warn(_("ignoring unknown color/effect %r "
297 298 "(configured in color.%s)\n")
298 299 % (e, status))
299 300 ui._styles[status] = ' '.join(good)
300 301
301 302 def _activeeffects(ui):
302 303 '''Return the effects map for the color mode set on the ui.'''
303 304 if ui._colormode == 'win32':
304 305 return w32effects
305 306 elif ui._colormode is not None:
306 307 return _effects
307 308 return {}
308 309
309 310 def valideffect(ui, effect):
310 311 'Determine if the effect is valid or not.'
311 312 return ((not ui._terminfoparams and effect in _activeeffects(ui))
312 313 or (effect in ui._terminfoparams
313 314 or effect[:-11] in ui._terminfoparams))
314 315
315 316 def _effect_str(ui, effect):
316 317 '''Helper function for render_effects().'''
317 318
318 319 bg = False
319 320 if effect.endswith('_background'):
320 321 bg = True
321 322 effect = effect[:-11]
322 323 try:
323 324 attr, val, termcode = ui._terminfoparams[effect]
324 325 except KeyError:
325 326 return ''
326 327 if attr:
327 328 if termcode:
328 329 return termcode
329 330 else:
330 331 return curses.tigetstr(pycompat.sysstr(val))
331 332 elif bg:
332 333 return curses.tparm(curses.tigetstr(r'setab'), val)
333 334 else:
334 335 return curses.tparm(curses.tigetstr(r'setaf'), val)
335 336
336 337 def _mergeeffects(text, start, stop):
337 338 """Insert start sequence at every occurrence of stop sequence
338 339
339 340 >>> s = _mergeeffects(b'cyan', b'[C]', b'|')
340 341 >>> s = _mergeeffects(s + b'yellow', b'[Y]', b'|')
341 342 >>> s = _mergeeffects(b'ma' + s + b'genta', b'[M]', b'|')
342 343 >>> s = _mergeeffects(b'red' + s, b'[R]', b'|')
343 344 >>> s
344 345 '[R]red[M]ma[Y][C]cyan|[R][M][Y]yellow|[R][M]genta|'
345 346 """
346 347 parts = []
347 348 for t in text.split(stop):
348 349 if not t:
349 350 continue
350 351 parts.extend([start, t, stop])
351 352 return ''.join(parts)
352 353
353 354 def _render_effects(ui, text, effects):
354 355 'Wrap text in commands to turn on each effect.'
355 356 if not text:
356 357 return text
357 358 if ui._terminfoparams:
358 359 start = ''.join(_effect_str(ui, effect)
359 360 for effect in ['none'] + effects.split())
360 361 stop = _effect_str(ui, 'none')
361 362 else:
362 363 activeeffects = _activeeffects(ui)
363 364 start = [pycompat.bytestr(activeeffects[e])
364 365 for e in ['none'] + effects.split()]
365 366 start = '\033[' + ';'.join(start) + 'm'
366 367 stop = '\033[' + pycompat.bytestr(activeeffects['none']) + 'm'
367 368 return _mergeeffects(text, start, stop)
368 369
369 370 _ansieffectre = re.compile(br'\x1b\[[0-9;]*m')
370 371
371 372 def stripeffects(text):
372 373 """Strip ANSI control codes which could be inserted by colorlabel()"""
373 374 return _ansieffectre.sub('', text)
374 375
375 376 def colorlabel(ui, msg, label):
376 377 """add color control code according to the mode"""
377 378 if ui._colormode == 'debug':
378 379 if label and msg:
379 380 if msg.endswith('\n'):
380 381 msg = "[%s|%s]\n" % (label, msg[:-1])
381 382 else:
382 383 msg = "[%s|%s]" % (label, msg)
383 384 elif ui._colormode is not None:
384 385 effects = []
385 386 for l in label.split():
386 387 s = ui._styles.get(l, '')
387 388 if s:
388 389 effects.append(s)
389 390 elif valideffect(ui, l):
390 391 effects.append(l)
391 392 effects = ' '.join(effects)
392 393 if effects:
393 394 msg = '\n'.join([_render_effects(ui, line, effects)
394 395 for line in msg.split('\n')])
395 396 return msg
396 397
397 398 w32effects = None
398 399 if pycompat.iswindows:
399 400 import ctypes
400 401
401 402 _kernel32 = ctypes.windll.kernel32
402 403
403 404 _WORD = ctypes.c_ushort
404 405
405 406 _INVALID_HANDLE_VALUE = -1
406 407
407 408 class _COORD(ctypes.Structure):
408 409 _fields_ = [('X', ctypes.c_short),
409 410 ('Y', ctypes.c_short)]
410 411
411 412 class _SMALL_RECT(ctypes.Structure):
412 413 _fields_ = [('Left', ctypes.c_short),
413 414 ('Top', ctypes.c_short),
414 415 ('Right', ctypes.c_short),
415 416 ('Bottom', ctypes.c_short)]
416 417
417 418 class _CONSOLE_SCREEN_BUFFER_INFO(ctypes.Structure):
418 419 _fields_ = [('dwSize', _COORD),
419 420 ('dwCursorPosition', _COORD),
420 421 ('wAttributes', _WORD),
421 422 ('srWindow', _SMALL_RECT),
422 423 ('dwMaximumWindowSize', _COORD)]
423 424
424 425 _STD_OUTPUT_HANDLE = 0xfffffff5 # (DWORD)-11
425 426 _STD_ERROR_HANDLE = 0xfffffff4 # (DWORD)-12
426 427
427 428 _FOREGROUND_BLUE = 0x0001
428 429 _FOREGROUND_GREEN = 0x0002
429 430 _FOREGROUND_RED = 0x0004
430 431 _FOREGROUND_INTENSITY = 0x0008
431 432
432 433 _BACKGROUND_BLUE = 0x0010
433 434 _BACKGROUND_GREEN = 0x0020
434 435 _BACKGROUND_RED = 0x0040
435 436 _BACKGROUND_INTENSITY = 0x0080
436 437
437 438 _COMMON_LVB_REVERSE_VIDEO = 0x4000
438 439 _COMMON_LVB_UNDERSCORE = 0x8000
439 440
440 441 # http://msdn.microsoft.com/en-us/library/ms682088%28VS.85%29.aspx
441 442 w32effects = {
442 443 'none': -1,
443 444 'black': 0,
444 445 'red': _FOREGROUND_RED,
445 446 'green': _FOREGROUND_GREEN,
446 447 'yellow': _FOREGROUND_RED | _FOREGROUND_GREEN,
447 448 'blue': _FOREGROUND_BLUE,
448 449 'magenta': _FOREGROUND_BLUE | _FOREGROUND_RED,
449 450 'cyan': _FOREGROUND_BLUE | _FOREGROUND_GREEN,
450 451 'white': _FOREGROUND_RED | _FOREGROUND_GREEN | _FOREGROUND_BLUE,
451 452 'bold': _FOREGROUND_INTENSITY,
452 453 'black_background': 0x100, # unused value > 0x0f
453 454 'red_background': _BACKGROUND_RED,
454 455 'green_background': _BACKGROUND_GREEN,
455 456 'yellow_background': _BACKGROUND_RED | _BACKGROUND_GREEN,
456 457 'blue_background': _BACKGROUND_BLUE,
457 458 'purple_background': _BACKGROUND_BLUE | _BACKGROUND_RED,
458 459 'cyan_background': _BACKGROUND_BLUE | _BACKGROUND_GREEN,
459 460 'white_background': (_BACKGROUND_RED | _BACKGROUND_GREEN |
460 461 _BACKGROUND_BLUE),
461 462 'bold_background': _BACKGROUND_INTENSITY,
462 463 'underline': _COMMON_LVB_UNDERSCORE, # double-byte charsets only
463 464 'inverse': _COMMON_LVB_REVERSE_VIDEO, # double-byte charsets only
464 465 }
465 466
466 467 passthrough = {_FOREGROUND_INTENSITY,
467 468 _BACKGROUND_INTENSITY,
468 469 _COMMON_LVB_UNDERSCORE,
469 470 _COMMON_LVB_REVERSE_VIDEO}
470 471
471 472 stdout = _kernel32.GetStdHandle(
472 473 _STD_OUTPUT_HANDLE) # don't close the handle returned
473 474 if stdout is None or stdout == _INVALID_HANDLE_VALUE:
474 475 w32effects = None
475 476 else:
476 477 csbi = _CONSOLE_SCREEN_BUFFER_INFO()
477 478 if not _kernel32.GetConsoleScreenBufferInfo(
478 479 stdout, ctypes.byref(csbi)):
479 480 # stdout may not support GetConsoleScreenBufferInfo()
480 481 # when called from subprocess or redirected
481 482 w32effects = None
482 483 else:
483 484 origattr = csbi.wAttributes
484 485 ansire = re.compile('\033\[([^m]*)m([^\033]*)(.*)',
485 486 re.MULTILINE | re.DOTALL)
486 487
487 488 def win32print(ui, writefunc, *msgs, **opts):
488 489 for text in msgs:
489 490 _win32print(ui, text, writefunc, **opts)
490 491
491 492 def _win32print(ui, text, writefunc, **opts):
492 493 label = opts.get(r'label', '')
493 494 attr = origattr
494 495
495 496 def mapcolor(val, attr):
496 497 if val == -1:
497 498 return origattr
498 499 elif val in passthrough:
499 500 return attr | val
500 501 elif val > 0x0f:
501 502 return (val & 0x70) | (attr & 0x8f)
502 503 else:
503 504 return (val & 0x07) | (attr & 0xf8)
504 505
505 506 # determine console attributes based on labels
506 507 for l in label.split():
507 508 style = ui._styles.get(l, '')
508 509 for effect in style.split():
509 510 try:
510 511 attr = mapcolor(w32effects[effect], attr)
511 512 except KeyError:
512 513 # w32effects could not have certain attributes so we skip
513 514 # them if not found
514 515 pass
515 516 # hack to ensure regexp finds data
516 517 if not text.startswith('\033['):
517 518 text = '\033[m' + text
518 519
519 520 # Look for ANSI-like codes embedded in text
520 521 m = re.match(ansire, text)
521 522
522 523 try:
523 524 while m:
524 525 for sattr in m.group(1).split(';'):
525 526 if sattr:
526 527 attr = mapcolor(int(sattr), attr)
527 528 ui.flush()
528 529 _kernel32.SetConsoleTextAttribute(stdout, attr)
529 530 writefunc(m.group(2), **opts)
530 531 m = re.match(ansire, m.group(3))
531 532 finally:
532 533 # Explicitly reset original attributes
533 534 ui.flush()
534 535 _kernel32.SetConsoleTextAttribute(stdout, origattr)
@@ -1,537 +1,537 b''
1 1 # commandserver.py - communicate with Mercurial's API over a pipe
2 2 #
3 3 # Copyright Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import errno
11 11 import gc
12 12 import os
13 13 import random
14 14 import signal
15 15 import socket
16 16 import struct
17 17 import traceback
18 18
19 19 try:
20 20 import selectors
21 21 selectors.BaseSelector
22 22 except ImportError:
23 23 from .thirdparty import selectors2 as selectors
24 24
25 25 from .i18n import _
26 26 from . import (
27 27 encoding,
28 28 error,
29 29 pycompat,
30 30 util,
31 31 )
32 32 from .utils import (
33 33 procutil,
34 34 )
35 35
36 36 logfile = None
37 37
38 38 def log(*args):
39 39 if not logfile:
40 40 return
41 41
42 42 for a in args:
43 43 logfile.write(str(a))
44 44
45 45 logfile.flush()
46 46
47 47 class channeledoutput(object):
48 48 """
49 49 Write data to out in the following format:
50 50
51 51 data length (unsigned int),
52 52 data
53 53 """
54 54 def __init__(self, out, channel):
55 55 self.out = out
56 56 self.channel = channel
57 57
58 58 @property
59 59 def name(self):
60 60 return '<%c-channel>' % self.channel
61 61
62 62 def write(self, data):
63 63 if not data:
64 64 return
65 65 # single write() to guarantee the same atomicity as the underlying file
66 66 self.out.write(struct.pack('>cI', self.channel, len(data)) + data)
67 67 self.out.flush()
68 68
69 69 def __getattr__(self, attr):
70 70 if attr in ('isatty', 'fileno', 'tell', 'seek'):
71 71 raise AttributeError(attr)
72 72 return getattr(self.out, attr)
73 73
74 74 class channeledinput(object):
75 75 """
76 76 Read data from in_.
77 77
78 78 Requests for input are written to out in the following format:
79 79 channel identifier - 'I' for plain input, 'L' line based (1 byte)
80 80 how many bytes to send at most (unsigned int),
81 81
82 82 The client replies with:
83 83 data length (unsigned int), 0 meaning EOF
84 84 data
85 85 """
86 86
87 87 maxchunksize = 4 * 1024
88 88
89 89 def __init__(self, in_, out, channel):
90 90 self.in_ = in_
91 91 self.out = out
92 92 self.channel = channel
93 93
94 94 @property
95 95 def name(self):
96 96 return '<%c-channel>' % self.channel
97 97
98 98 def read(self, size=-1):
99 99 if size < 0:
100 100 # if we need to consume all the clients input, ask for 4k chunks
101 101 # so the pipe doesn't fill up risking a deadlock
102 102 size = self.maxchunksize
103 103 s = self._read(size, self.channel)
104 104 buf = s
105 105 while s:
106 106 s = self._read(size, self.channel)
107 107 buf += s
108 108
109 109 return buf
110 110 else:
111 111 return self._read(size, self.channel)
112 112
113 113 def _read(self, size, channel):
114 114 if not size:
115 115 return ''
116 116 assert size > 0
117 117
118 118 # tell the client we need at most size bytes
119 119 self.out.write(struct.pack('>cI', channel, size))
120 120 self.out.flush()
121 121
122 122 length = self.in_.read(4)
123 123 length = struct.unpack('>I', length)[0]
124 124 if not length:
125 125 return ''
126 126 else:
127 127 return self.in_.read(length)
128 128
129 129 def readline(self, size=-1):
130 130 if size < 0:
131 131 size = self.maxchunksize
132 132 s = self._read(size, 'L')
133 133 buf = s
134 134 # keep asking for more until there's either no more or
135 135 # we got a full line
136 136 while s and s[-1] != '\n':
137 137 s = self._read(size, 'L')
138 138 buf += s
139 139
140 140 return buf
141 141 else:
142 142 return self._read(size, 'L')
143 143
144 144 def __iter__(self):
145 145 return self
146 146
147 147 def next(self):
148 148 l = self.readline()
149 149 if not l:
150 150 raise StopIteration
151 151 return l
152 152
153 153 def __getattr__(self, attr):
154 154 if attr in ('isatty', 'fileno', 'tell', 'seek'):
155 155 raise AttributeError(attr)
156 156 return getattr(self.in_, attr)
157 157
158 158 class server(object):
159 159 """
160 160 Listens for commands on fin, runs them and writes the output on a channel
161 161 based stream to fout.
162 162 """
163 163 def __init__(self, ui, repo, fin, fout):
164 164 self.cwd = pycompat.getcwd()
165 165
166 166 # developer config: cmdserver.log
167 167 logpath = ui.config("cmdserver", "log")
168 168 if logpath:
169 169 global logfile
170 170 if logpath == '-':
171 171 # write log on a special 'd' (debug) channel
172 172 logfile = channeledoutput(fout, 'd')
173 173 else:
174 174 logfile = open(logpath, 'a')
175 175
176 176 if repo:
177 177 # the ui here is really the repo ui so take its baseui so we don't
178 178 # end up with its local configuration
179 179 self.ui = repo.baseui
180 180 self.repo = repo
181 181 self.repoui = repo.ui
182 182 else:
183 183 self.ui = ui
184 184 self.repo = self.repoui = None
185 185
186 186 self.cerr = channeledoutput(fout, 'e')
187 187 self.cout = channeledoutput(fout, 'o')
188 188 self.cin = channeledinput(fin, fout, 'I')
189 189 self.cresult = channeledoutput(fout, 'r')
190 190
191 191 self.client = fin
192 192
193 193 def cleanup(self):
194 194 """release and restore resources taken during server session"""
195 195
196 196 def _read(self, size):
197 197 if not size:
198 198 return ''
199 199
200 200 data = self.client.read(size)
201 201
202 202 # is the other end closed?
203 203 if not data:
204 204 raise EOFError
205 205
206 206 return data
207 207
208 208 def _readstr(self):
209 209 """read a string from the channel
210 210
211 211 format:
212 212 data length (uint32), data
213 213 """
214 214 length = struct.unpack('>I', self._read(4))[0]
215 215 if not length:
216 216 return ''
217 217 return self._read(length)
218 218
219 219 def _readlist(self):
220 220 """read a list of NULL separated strings from the channel"""
221 221 s = self._readstr()
222 222 if s:
223 223 return s.split('\0')
224 224 else:
225 225 return []
226 226
227 227 def runcommand(self):
228 228 """ reads a list of \0 terminated arguments, executes
229 229 and writes the return code to the result channel """
230 230 from . import dispatch # avoid cycle
231 231
232 232 args = self._readlist()
233 233
234 234 # copy the uis so changes (e.g. --config or --verbose) don't
235 235 # persist between requests
236 236 copiedui = self.ui.copy()
237 237 uis = [copiedui]
238 238 if self.repo:
239 239 self.repo.baseui = copiedui
240 240 # clone ui without using ui.copy because this is protected
241 241 repoui = self.repoui.__class__(self.repoui)
242 242 repoui.copy = copiedui.copy # redo copy protection
243 243 uis.append(repoui)
244 244 self.repo.ui = self.repo.dirstate._ui = repoui
245 245 self.repo.invalidateall()
246 246
247 247 for ui in uis:
248 248 ui.resetstate()
249 249 # any kind of interaction must use server channels, but chg may
250 250 # replace channels by fully functional tty files. so nontty is
251 251 # enforced only if cin is a channel.
252 252 if not util.safehasattr(self.cin, 'fileno'):
253 253 ui.setconfig('ui', 'nontty', 'true', 'commandserver')
254 254
255 255 req = dispatch.request(args[:], copiedui, self.repo, self.cin,
256 256 self.cout, self.cerr)
257 257
258 258 try:
259 259 ret = dispatch.dispatch(req) & 255
260 260 self.cresult.write(struct.pack('>i', int(ret)))
261 261 finally:
262 262 # restore old cwd
263 263 if '--cwd' in args:
264 264 os.chdir(self.cwd)
265 265
266 266 def getencoding(self):
267 267 """ writes the current encoding to the result channel """
268 268 self.cresult.write(encoding.encoding)
269 269
270 270 def serveone(self):
271 271 cmd = self.client.readline()[:-1]
272 272 if cmd:
273 273 handler = self.capabilities.get(cmd)
274 274 if handler:
275 275 handler(self)
276 276 else:
277 277 # clients are expected to check what commands are supported by
278 278 # looking at the servers capabilities
279 279 raise error.Abort(_('unknown command %s') % cmd)
280 280
281 281 return cmd != ''
282 282
283 283 capabilities = {'runcommand': runcommand,
284 284 'getencoding': getencoding}
285 285
286 286 def serve(self):
287 287 hellomsg = 'capabilities: ' + ' '.join(sorted(self.capabilities))
288 288 hellomsg += '\n'
289 289 hellomsg += 'encoding: ' + encoding.encoding
290 290 hellomsg += '\n'
291 291 hellomsg += 'pid: %d' % procutil.getpid()
292 292 if util.safehasattr(os, 'getpgid'):
293 293 hellomsg += '\n'
294 294 hellomsg += 'pgid: %d' % os.getpgid(0)
295 295
296 296 # write the hello msg in -one- chunk
297 297 self.cout.write(hellomsg)
298 298
299 299 try:
300 300 while self.serveone():
301 301 pass
302 302 except EOFError:
303 303 # we'll get here if the client disconnected while we were reading
304 304 # its request
305 305 return 1
306 306
307 307 return 0
308 308
309 309 class pipeservice(object):
310 310 def __init__(self, ui, repo, opts):
311 311 self.ui = ui
312 312 self.repo = repo
313 313
314 314 def init(self):
315 315 pass
316 316
317 317 def run(self):
318 318 ui = self.ui
319 319 # redirect stdio to null device so that broken extensions or in-process
320 320 # hooks will never cause corruption of channel protocol.
321 321 with procutil.protectedstdio(ui.fin, ui.fout) as (fin, fout):
322 322 try:
323 323 sv = server(ui, self.repo, fin, fout)
324 324 return sv.serve()
325 325 finally:
326 326 sv.cleanup()
327 327
328 328 def _initworkerprocess():
329 329 # use a different process group from the master process, in order to:
330 330 # 1. make the current process group no longer "orphaned" (because the
331 331 # parent of this process is in a different process group while
332 332 # remains in a same session)
333 333 # according to POSIX 2.2.2.52, orphaned process group will ignore
334 334 # terminal-generated stop signals like SIGTSTP (Ctrl+Z), which will
335 335 # cause trouble for things like ncurses.
336 336 # 2. the client can use kill(-pgid, sig) to simulate terminal-generated
337 337 # SIGINT (Ctrl+C) and process-exit-generated SIGHUP. our child
338 338 # processes like ssh will be killed properly, without affecting
339 339 # unrelated processes.
340 340 os.setpgid(0, 0)
341 341 # change random state otherwise forked request handlers would have a
342 342 # same state inherited from parent.
343 343 random.seed()
344 344
345 345 def _serverequest(ui, repo, conn, createcmdserver):
346 346 fin = conn.makefile('rb')
347 347 fout = conn.makefile('wb')
348 348 sv = None
349 349 try:
350 350 sv = createcmdserver(repo, conn, fin, fout)
351 351 try:
352 352 sv.serve()
353 353 # handle exceptions that may be raised by command server. most of
354 354 # known exceptions are caught by dispatch.
355 355 except error.Abort as inst:
356 ui.warn(_('abort: %s\n') % inst)
356 ui.error(_('abort: %s\n') % inst)
357 357 except IOError as inst:
358 358 if inst.errno != errno.EPIPE:
359 359 raise
360 360 except KeyboardInterrupt:
361 361 pass
362 362 finally:
363 363 sv.cleanup()
364 364 except: # re-raises
365 365 # also write traceback to error channel. otherwise client cannot
366 366 # see it because it is written to server's stderr by default.
367 367 if sv:
368 368 cerr = sv.cerr
369 369 else:
370 370 cerr = channeledoutput(fout, 'e')
371 371 traceback.print_exc(file=cerr)
372 372 raise
373 373 finally:
374 374 fin.close()
375 375 try:
376 376 fout.close() # implicit flush() may cause another EPIPE
377 377 except IOError as inst:
378 378 if inst.errno != errno.EPIPE:
379 379 raise
380 380
381 381 class unixservicehandler(object):
382 382 """Set of pluggable operations for unix-mode services
383 383
384 384 Almost all methods except for createcmdserver() are called in the main
385 385 process. You can't pass mutable resource back from createcmdserver().
386 386 """
387 387
388 388 pollinterval = None
389 389
390 390 def __init__(self, ui):
391 391 self.ui = ui
392 392
393 393 def bindsocket(self, sock, address):
394 394 util.bindunixsocket(sock, address)
395 395 sock.listen(socket.SOMAXCONN)
396 396 self.ui.status(_('listening at %s\n') % address)
397 397 self.ui.flush() # avoid buffering of status message
398 398
399 399 def unlinksocket(self, address):
400 400 os.unlink(address)
401 401
402 402 def shouldexit(self):
403 403 """True if server should shut down; checked per pollinterval"""
404 404 return False
405 405
406 406 def newconnection(self):
407 407 """Called when main process notices new connection"""
408 408
409 409 def createcmdserver(self, repo, conn, fin, fout):
410 410 """Create new command server instance; called in the process that
411 411 serves for the current connection"""
412 412 return server(self.ui, repo, fin, fout)
413 413
414 414 class unixforkingservice(object):
415 415 """
416 416 Listens on unix domain socket and forks server per connection
417 417 """
418 418
419 419 def __init__(self, ui, repo, opts, handler=None):
420 420 self.ui = ui
421 421 self.repo = repo
422 422 self.address = opts['address']
423 423 if not util.safehasattr(socket, 'AF_UNIX'):
424 424 raise error.Abort(_('unsupported platform'))
425 425 if not self.address:
426 426 raise error.Abort(_('no socket path specified with --address'))
427 427 self._servicehandler = handler or unixservicehandler(ui)
428 428 self._sock = None
429 429 self._oldsigchldhandler = None
430 430 self._workerpids = set() # updated by signal handler; do not iterate
431 431 self._socketunlinked = None
432 432
433 433 def init(self):
434 434 self._sock = socket.socket(socket.AF_UNIX)
435 435 self._servicehandler.bindsocket(self._sock, self.address)
436 436 if util.safehasattr(procutil, 'unblocksignal'):
437 437 procutil.unblocksignal(signal.SIGCHLD)
438 438 o = signal.signal(signal.SIGCHLD, self._sigchldhandler)
439 439 self._oldsigchldhandler = o
440 440 self._socketunlinked = False
441 441
442 442 def _unlinksocket(self):
443 443 if not self._socketunlinked:
444 444 self._servicehandler.unlinksocket(self.address)
445 445 self._socketunlinked = True
446 446
447 447 def _cleanup(self):
448 448 signal.signal(signal.SIGCHLD, self._oldsigchldhandler)
449 449 self._sock.close()
450 450 self._unlinksocket()
451 451 # don't kill child processes as they have active clients, just wait
452 452 self._reapworkers(0)
453 453
454 454 def run(self):
455 455 try:
456 456 self._mainloop()
457 457 finally:
458 458 self._cleanup()
459 459
460 460 def _mainloop(self):
461 461 exiting = False
462 462 h = self._servicehandler
463 463 selector = selectors.DefaultSelector()
464 464 selector.register(self._sock, selectors.EVENT_READ)
465 465 while True:
466 466 if not exiting and h.shouldexit():
467 467 # clients can no longer connect() to the domain socket, so
468 468 # we stop queuing new requests.
469 469 # for requests that are queued (connect()-ed, but haven't been
470 470 # accept()-ed), handle them before exit. otherwise, clients
471 471 # waiting for recv() will receive ECONNRESET.
472 472 self._unlinksocket()
473 473 exiting = True
474 474 ready = selector.select(timeout=h.pollinterval)
475 475 if not ready:
476 476 # only exit if we completed all queued requests
477 477 if exiting:
478 478 break
479 479 continue
480 480 try:
481 481 conn, _addr = self._sock.accept()
482 482 except socket.error as inst:
483 483 if inst.args[0] == errno.EINTR:
484 484 continue
485 485 raise
486 486
487 487 pid = os.fork()
488 488 if pid:
489 489 try:
490 490 self.ui.debug('forked worker process (pid=%d)\n' % pid)
491 491 self._workerpids.add(pid)
492 492 h.newconnection()
493 493 finally:
494 494 conn.close() # release handle in parent process
495 495 else:
496 496 try:
497 497 selector.close()
498 498 self._sock.close()
499 499 self._runworker(conn)
500 500 conn.close()
501 501 os._exit(0)
502 502 except: # never return, hence no re-raises
503 503 try:
504 504 self.ui.traceback(force=True)
505 505 finally:
506 506 os._exit(255)
507 507 selector.close()
508 508
509 509 def _sigchldhandler(self, signal, frame):
510 510 self._reapworkers(os.WNOHANG)
511 511
512 512 def _reapworkers(self, options):
513 513 while self._workerpids:
514 514 try:
515 515 pid, _status = os.waitpid(-1, options)
516 516 except OSError as inst:
517 517 if inst.errno == errno.EINTR:
518 518 continue
519 519 if inst.errno != errno.ECHILD:
520 520 raise
521 521 # no child processes at all (reaped by other waitpid()?)
522 522 self._workerpids.clear()
523 523 return
524 524 if pid == 0:
525 525 # no waitable child processes
526 526 return
527 527 self.ui.debug('worker process exited (pid=%d)\n' % pid)
528 528 self._workerpids.discard(pid)
529 529
530 530 def _runworker(self, conn):
531 531 signal.signal(signal.SIGCHLD, self._oldsigchldhandler)
532 532 _initworkerprocess()
533 533 h = self._servicehandler
534 534 try:
535 535 _serverequest(self.ui, self.repo, conn, h.createcmdserver)
536 536 finally:
537 537 gc.collect() # trigger __del__ since worker process uses os._exit
@@ -1,1066 +1,1066 b''
1 1 # dispatch.py - command dispatching for mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import, print_function
9 9
10 10 import difflib
11 11 import errno
12 12 import getopt
13 13 import os
14 14 import pdb
15 15 import re
16 16 import signal
17 17 import sys
18 18 import time
19 19 import traceback
20 20
21 21
22 22 from .i18n import _
23 23
24 24 from . import (
25 25 cmdutil,
26 26 color,
27 27 commands,
28 28 demandimport,
29 29 encoding,
30 30 error,
31 31 extensions,
32 32 fancyopts,
33 33 help,
34 34 hg,
35 35 hook,
36 36 profiling,
37 37 pycompat,
38 38 scmutil,
39 39 ui as uimod,
40 40 util,
41 41 )
42 42
43 43 from .utils import (
44 44 procutil,
45 45 stringutil,
46 46 )
47 47
48 48 class request(object):
49 49 def __init__(self, args, ui=None, repo=None, fin=None, fout=None,
50 50 ferr=None, prereposetups=None):
51 51 self.args = args
52 52 self.ui = ui
53 53 self.repo = repo
54 54
55 55 # input/output/error streams
56 56 self.fin = fin
57 57 self.fout = fout
58 58 self.ferr = ferr
59 59
60 60 # remember options pre-parsed by _earlyparseopts()
61 61 self.earlyoptions = {}
62 62
63 63 # reposetups which run before extensions, useful for chg to pre-fill
64 64 # low-level repo state (for example, changelog) before extensions.
65 65 self.prereposetups = prereposetups or []
66 66
67 67 def _runexithandlers(self):
68 68 exc = None
69 69 handlers = self.ui._exithandlers
70 70 try:
71 71 while handlers:
72 72 func, args, kwargs = handlers.pop()
73 73 try:
74 74 func(*args, **kwargs)
75 75 except: # re-raises below
76 76 if exc is None:
77 77 exc = sys.exc_info()[1]
78 78 self.ui.warn(('error in exit handlers:\n'))
79 79 self.ui.traceback(force=True)
80 80 finally:
81 81 if exc is not None:
82 82 raise exc
83 83
84 84 def run():
85 85 "run the command in sys.argv"
86 86 initstdio()
87 87 req = request(pycompat.sysargv[1:])
88 88 err = None
89 89 try:
90 90 status = dispatch(req)
91 91 except error.StdioError as e:
92 92 err = e
93 93 status = -1
94 94
95 95 # In all cases we try to flush stdio streams.
96 96 if util.safehasattr(req.ui, 'fout'):
97 97 try:
98 98 req.ui.fout.flush()
99 99 except IOError as e:
100 100 err = e
101 101 status = -1
102 102
103 103 if util.safehasattr(req.ui, 'ferr'):
104 104 try:
105 105 if err is not None and err.errno != errno.EPIPE:
106 106 req.ui.ferr.write('abort: %s\n' %
107 107 encoding.strtolocal(err.strerror))
108 108 req.ui.ferr.flush()
109 109 # There's not much we can do about an I/O error here. So (possibly)
110 110 # change the status code and move on.
111 111 except IOError:
112 112 status = -1
113 113
114 114 _silencestdio()
115 115 sys.exit(status & 255)
116 116
117 117 if pycompat.ispy3:
118 118 def initstdio():
119 119 pass
120 120
121 121 def _silencestdio():
122 122 for fp in (sys.stdout, sys.stderr):
123 123 # Check if the file is okay
124 124 try:
125 125 fp.flush()
126 126 continue
127 127 except IOError:
128 128 pass
129 129 # Otherwise mark it as closed to silence "Exception ignored in"
130 130 # message emitted by the interpreter finalizer. Be careful to
131 131 # not close procutil.stdout, which may be a fdopen-ed file object
132 132 # and its close() actually closes the underlying file descriptor.
133 133 try:
134 134 fp.close()
135 135 except IOError:
136 136 pass
137 137 else:
138 138 def initstdio():
139 139 for fp in (sys.stdin, sys.stdout, sys.stderr):
140 140 procutil.setbinary(fp)
141 141
142 142 def _silencestdio():
143 143 pass
144 144
145 145 def _getsimilar(symbols, value):
146 146 sim = lambda x: difflib.SequenceMatcher(None, value, x).ratio()
147 147 # The cutoff for similarity here is pretty arbitrary. It should
148 148 # probably be investigated and tweaked.
149 149 return [s for s in symbols if sim(s) > 0.6]
150 150
151 151 def _reportsimilar(write, similar):
152 152 if len(similar) == 1:
153 153 write(_("(did you mean %s?)\n") % similar[0])
154 154 elif similar:
155 155 ss = ", ".join(sorted(similar))
156 156 write(_("(did you mean one of %s?)\n") % ss)
157 157
158 158 def _formatparse(write, inst):
159 159 similar = []
160 160 if isinstance(inst, error.UnknownIdentifier):
161 161 # make sure to check fileset first, as revset can invoke fileset
162 162 similar = _getsimilar(inst.symbols, inst.function)
163 163 if len(inst.args) > 1:
164 164 write(_("hg: parse error at %s: %s\n") %
165 165 (pycompat.bytestr(inst.args[1]), inst.args[0]))
166 166 if inst.args[0].startswith(' '):
167 167 write(_("unexpected leading whitespace\n"))
168 168 else:
169 169 write(_("hg: parse error: %s\n") % inst.args[0])
170 170 _reportsimilar(write, similar)
171 171 if inst.hint:
172 172 write(_("(%s)\n") % inst.hint)
173 173
174 174 def _formatargs(args):
175 175 return ' '.join(procutil.shellquote(a) for a in args)
176 176
177 177 def dispatch(req):
178 178 """run the command specified in req.args; returns an integer status code"""
179 179 if req.ferr:
180 180 ferr = req.ferr
181 181 elif req.ui:
182 182 ferr = req.ui.ferr
183 183 else:
184 184 ferr = procutil.stderr
185 185
186 186 try:
187 187 if not req.ui:
188 188 req.ui = uimod.ui.load()
189 189 req.earlyoptions.update(_earlyparseopts(req.ui, req.args))
190 190 if req.earlyoptions['traceback']:
191 191 req.ui.setconfig('ui', 'traceback', 'on', '--traceback')
192 192
193 193 # set ui streams from the request
194 194 if req.fin:
195 195 req.ui.fin = req.fin
196 196 if req.fout:
197 197 req.ui.fout = req.fout
198 198 if req.ferr:
199 199 req.ui.ferr = req.ferr
200 200 except error.Abort as inst:
201 201 ferr.write(_("abort: %s\n") % inst)
202 202 if inst.hint:
203 203 ferr.write(_("(%s)\n") % inst.hint)
204 204 return -1
205 205 except error.ParseError as inst:
206 206 _formatparse(ferr.write, inst)
207 207 return -1
208 208
209 209 msg = _formatargs(req.args)
210 210 starttime = util.timer()
211 211 ret = 1 # default of Python exit code on unhandled exception
212 212 try:
213 213 ret = _runcatch(req) or 0
214 214 except error.ProgrammingError as inst:
215 req.ui.warn(_('** ProgrammingError: %s\n') % inst)
215 req.ui.error(_('** ProgrammingError: %s\n') % inst)
216 216 if inst.hint:
217 req.ui.warn(_('** (%s)\n') % inst.hint)
217 req.ui.error(_('** (%s)\n') % inst.hint)
218 218 raise
219 219 except KeyboardInterrupt as inst:
220 220 try:
221 221 if isinstance(inst, error.SignalInterrupt):
222 222 msg = _("killed!\n")
223 223 else:
224 224 msg = _("interrupted!\n")
225 req.ui.warn(msg)
225 req.ui.error(msg)
226 226 except error.SignalInterrupt:
227 227 # maybe pager would quit without consuming all the output, and
228 228 # SIGPIPE was raised. we cannot print anything in this case.
229 229 pass
230 230 except IOError as inst:
231 231 if inst.errno != errno.EPIPE:
232 232 raise
233 233 ret = -1
234 234 finally:
235 235 duration = util.timer() - starttime
236 236 req.ui.flush()
237 237 if req.ui.logblockedtimes:
238 238 req.ui._blockedtimes['command_duration'] = duration * 1000
239 239 req.ui.log('uiblocked', 'ui blocked ms',
240 240 **pycompat.strkwargs(req.ui._blockedtimes))
241 241 req.ui.log("commandfinish", "%s exited %d after %0.2f seconds\n",
242 242 msg, ret & 255, duration)
243 243 try:
244 244 req._runexithandlers()
245 245 except: # exiting, so no re-raises
246 246 ret = ret or -1
247 247 return ret
248 248
249 249 def _runcatch(req):
250 250 def catchterm(*args):
251 251 raise error.SignalInterrupt
252 252
253 253 ui = req.ui
254 254 try:
255 255 for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM':
256 256 num = getattr(signal, name, None)
257 257 if num:
258 258 signal.signal(num, catchterm)
259 259 except ValueError:
260 260 pass # happens if called in a thread
261 261
262 262 def _runcatchfunc():
263 263 realcmd = None
264 264 try:
265 265 cmdargs = fancyopts.fancyopts(req.args[:], commands.globalopts, {})
266 266 cmd = cmdargs[0]
267 267 aliases, entry = cmdutil.findcmd(cmd, commands.table, False)
268 268 realcmd = aliases[0]
269 269 except (error.UnknownCommand, error.AmbiguousCommand,
270 270 IndexError, getopt.GetoptError):
271 271 # Don't handle this here. We know the command is
272 272 # invalid, but all we're worried about for now is that
273 273 # it's not a command that server operators expect to
274 274 # be safe to offer to users in a sandbox.
275 275 pass
276 276 if realcmd == 'serve' and '--stdio' in cmdargs:
277 277 # We want to constrain 'hg serve --stdio' instances pretty
278 278 # closely, as many shared-ssh access tools want to grant
279 279 # access to run *only* 'hg -R $repo serve --stdio'. We
280 280 # restrict to exactly that set of arguments, and prohibit
281 281 # any repo name that starts with '--' to prevent
282 282 # shenanigans wherein a user does something like pass
283 283 # --debugger or --config=ui.debugger=1 as a repo
284 284 # name. This used to actually run the debugger.
285 285 if (len(req.args) != 4 or
286 286 req.args[0] != '-R' or
287 287 req.args[1].startswith('--') or
288 288 req.args[2] != 'serve' or
289 289 req.args[3] != '--stdio'):
290 290 raise error.Abort(
291 291 _('potentially unsafe serve --stdio invocation: %s') %
292 292 (stringutil.pprint(req.args),))
293 293
294 294 try:
295 295 debugger = 'pdb'
296 296 debugtrace = {
297 297 'pdb': pdb.set_trace
298 298 }
299 299 debugmortem = {
300 300 'pdb': pdb.post_mortem
301 301 }
302 302
303 303 # read --config before doing anything else
304 304 # (e.g. to change trust settings for reading .hg/hgrc)
305 305 cfgs = _parseconfig(req.ui, req.earlyoptions['config'])
306 306
307 307 if req.repo:
308 308 # copy configs that were passed on the cmdline (--config) to
309 309 # the repo ui
310 310 for sec, name, val in cfgs:
311 311 req.repo.ui.setconfig(sec, name, val, source='--config')
312 312
313 313 # developer config: ui.debugger
314 314 debugger = ui.config("ui", "debugger")
315 315 debugmod = pdb
316 316 if not debugger or ui.plain():
317 317 # if we are in HGPLAIN mode, then disable custom debugging
318 318 debugger = 'pdb'
319 319 elif req.earlyoptions['debugger']:
320 320 # This import can be slow for fancy debuggers, so only
321 321 # do it when absolutely necessary, i.e. when actual
322 322 # debugging has been requested
323 323 with demandimport.deactivated():
324 324 try:
325 325 debugmod = __import__(debugger)
326 326 except ImportError:
327 327 pass # Leave debugmod = pdb
328 328
329 329 debugtrace[debugger] = debugmod.set_trace
330 330 debugmortem[debugger] = debugmod.post_mortem
331 331
332 332 # enter the debugger before command execution
333 333 if req.earlyoptions['debugger']:
334 334 ui.warn(_("entering debugger - "
335 335 "type c to continue starting hg or h for help\n"))
336 336
337 337 if (debugger != 'pdb' and
338 338 debugtrace[debugger] == debugtrace['pdb']):
339 339 ui.warn(_("%s debugger specified "
340 340 "but its module was not found\n") % debugger)
341 341 with demandimport.deactivated():
342 342 debugtrace[debugger]()
343 343 try:
344 344 return _dispatch(req)
345 345 finally:
346 346 ui.flush()
347 347 except: # re-raises
348 348 # enter the debugger when we hit an exception
349 349 if req.earlyoptions['debugger']:
350 350 traceback.print_exc()
351 351 debugmortem[debugger](sys.exc_info()[2])
352 352 raise
353 353
354 354 return _callcatch(ui, _runcatchfunc)
355 355
356 356 def _callcatch(ui, func):
357 357 """like scmutil.callcatch but handles more high-level exceptions about
358 358 config parsing and commands. besides, use handlecommandexception to handle
359 359 uncaught exceptions.
360 360 """
361 361 try:
362 362 return scmutil.callcatch(ui, func)
363 363 except error.AmbiguousCommand as inst:
364 364 ui.warn(_("hg: command '%s' is ambiguous:\n %s\n") %
365 365 (inst.args[0], " ".join(inst.args[1])))
366 366 except error.CommandError as inst:
367 367 if inst.args[0]:
368 368 ui.pager('help')
369 369 msgbytes = pycompat.bytestr(inst.args[1])
370 370 ui.warn(_("hg %s: %s\n") % (inst.args[0], msgbytes))
371 371 commands.help_(ui, inst.args[0], full=False, command=True)
372 372 else:
373 373 ui.pager('help')
374 374 ui.warn(_("hg: %s\n") % inst.args[1])
375 375 commands.help_(ui, 'shortlist')
376 376 except error.ParseError as inst:
377 377 _formatparse(ui.warn, inst)
378 378 return -1
379 379 except error.UnknownCommand as inst:
380 380 nocmdmsg = _("hg: unknown command '%s'\n") % inst.args[0]
381 381 try:
382 382 # check if the command is in a disabled extension
383 383 # (but don't check for extensions themselves)
384 384 formatted = help.formattedhelp(ui, commands, inst.args[0],
385 385 unknowncmd=True)
386 386 ui.warn(nocmdmsg)
387 387 ui.write(formatted)
388 388 except (error.UnknownCommand, error.Abort):
389 389 suggested = False
390 390 if len(inst.args) == 2:
391 391 sim = _getsimilar(inst.args[1], inst.args[0])
392 392 if sim:
393 393 ui.warn(nocmdmsg)
394 394 _reportsimilar(ui.warn, sim)
395 395 suggested = True
396 396 if not suggested:
397 397 ui.pager('help')
398 398 ui.warn(nocmdmsg)
399 399 commands.help_(ui, 'shortlist')
400 400 except IOError:
401 401 raise
402 402 except KeyboardInterrupt:
403 403 raise
404 404 except: # probably re-raises
405 405 if not handlecommandexception(ui):
406 406 raise
407 407
408 408 return -1
409 409
410 410 def aliasargs(fn, givenargs):
411 411 args = []
412 412 # only care about alias 'args', ignore 'args' set by extensions.wrapfunction
413 413 if not util.safehasattr(fn, '_origfunc'):
414 414 args = getattr(fn, 'args', args)
415 415 if args:
416 416 cmd = ' '.join(map(procutil.shellquote, args))
417 417
418 418 nums = []
419 419 def replacer(m):
420 420 num = int(m.group(1)) - 1
421 421 nums.append(num)
422 422 if num < len(givenargs):
423 423 return givenargs[num]
424 424 raise error.Abort(_('too few arguments for command alias'))
425 425 cmd = re.sub(br'\$(\d+|\$)', replacer, cmd)
426 426 givenargs = [x for i, x in enumerate(givenargs)
427 427 if i not in nums]
428 428 args = pycompat.shlexsplit(cmd)
429 429 return args + givenargs
430 430
431 431 def aliasinterpolate(name, args, cmd):
432 432 '''interpolate args into cmd for shell aliases
433 433
434 434 This also handles $0, $@ and "$@".
435 435 '''
436 436 # util.interpolate can't deal with "$@" (with quotes) because it's only
437 437 # built to match prefix + patterns.
438 438 replacemap = dict(('$%d' % (i + 1), arg) for i, arg in enumerate(args))
439 439 replacemap['$0'] = name
440 440 replacemap['$$'] = '$'
441 441 replacemap['$@'] = ' '.join(args)
442 442 # Typical Unix shells interpolate "$@" (with quotes) as all the positional
443 443 # parameters, separated out into words. Emulate the same behavior here by
444 444 # quoting the arguments individually. POSIX shells will then typically
445 445 # tokenize each argument into exactly one word.
446 446 replacemap['"$@"'] = ' '.join(procutil.shellquote(arg) for arg in args)
447 447 # escape '\$' for regex
448 448 regex = '|'.join(replacemap.keys()).replace('$', br'\$')
449 449 r = re.compile(regex)
450 450 return r.sub(lambda x: replacemap[x.group()], cmd)
451 451
452 452 class cmdalias(object):
453 453 def __init__(self, ui, name, definition, cmdtable, source):
454 454 self.name = self.cmd = name
455 455 self.cmdname = ''
456 456 self.definition = definition
457 457 self.fn = None
458 458 self.givenargs = []
459 459 self.opts = []
460 460 self.help = ''
461 461 self.badalias = None
462 462 self.unknowncmd = False
463 463 self.source = source
464 464
465 465 try:
466 466 aliases, entry = cmdutil.findcmd(self.name, cmdtable)
467 467 for alias, e in cmdtable.iteritems():
468 468 if e is entry:
469 469 self.cmd = alias
470 470 break
471 471 self.shadows = True
472 472 except error.UnknownCommand:
473 473 self.shadows = False
474 474
475 475 if not self.definition:
476 476 self.badalias = _("no definition for alias '%s'") % self.name
477 477 return
478 478
479 479 if self.definition.startswith('!'):
480 480 shdef = self.definition[1:]
481 481 self.shell = True
482 482 def fn(ui, *args):
483 483 env = {'HG_ARGS': ' '.join((self.name,) + args)}
484 484 def _checkvar(m):
485 485 if m.groups()[0] == '$':
486 486 return m.group()
487 487 elif int(m.groups()[0]) <= len(args):
488 488 return m.group()
489 489 else:
490 490 ui.debug("No argument found for substitution "
491 491 "of %i variable in alias '%s' definition.\n"
492 492 % (int(m.groups()[0]), self.name))
493 493 return ''
494 494 cmd = re.sub(br'\$(\d+|\$)', _checkvar, shdef)
495 495 cmd = aliasinterpolate(self.name, args, cmd)
496 496 return ui.system(cmd, environ=env,
497 497 blockedtag='alias_%s' % self.name)
498 498 self.fn = fn
499 499 self._populatehelp(ui, name, shdef, self.fn)
500 500 return
501 501
502 502 try:
503 503 args = pycompat.shlexsplit(self.definition)
504 504 except ValueError as inst:
505 505 self.badalias = (_("error in definition for alias '%s': %s")
506 506 % (self.name, stringutil.forcebytestr(inst)))
507 507 return
508 508 earlyopts, args = _earlysplitopts(args)
509 509 if earlyopts:
510 510 self.badalias = (_("error in definition for alias '%s': %s may "
511 511 "only be given on the command line")
512 512 % (self.name, '/'.join(pycompat.ziplist(*earlyopts)
513 513 [0])))
514 514 return
515 515 self.cmdname = cmd = args.pop(0)
516 516 self.givenargs = args
517 517
518 518 try:
519 519 tableentry = cmdutil.findcmd(cmd, cmdtable, False)[1]
520 520 if len(tableentry) > 2:
521 521 self.fn, self.opts, cmdhelp = tableentry
522 522 else:
523 523 self.fn, self.opts = tableentry
524 524 cmdhelp = None
525 525
526 526 self._populatehelp(ui, name, cmd, self.fn, cmdhelp)
527 527
528 528 except error.UnknownCommand:
529 529 self.badalias = (_("alias '%s' resolves to unknown command '%s'")
530 530 % (self.name, cmd))
531 531 self.unknowncmd = True
532 532 except error.AmbiguousCommand:
533 533 self.badalias = (_("alias '%s' resolves to ambiguous command '%s'")
534 534 % (self.name, cmd))
535 535
536 536 def _populatehelp(self, ui, name, cmd, fn, defaulthelp=None):
537 537 # confine strings to be passed to i18n.gettext()
538 538 cfg = {}
539 539 for k in ('doc', 'help'):
540 540 v = ui.config('alias', '%s:%s' % (name, k), None)
541 541 if v is None:
542 542 continue
543 543 if not encoding.isasciistr(v):
544 544 self.badalias = (_("non-ASCII character in alias definition "
545 545 "'%s:%s'") % (name, k))
546 546 return
547 547 cfg[k] = v
548 548
549 549 self.help = cfg.get('help', defaulthelp or '')
550 550 if self.help and self.help.startswith("hg " + cmd):
551 551 # drop prefix in old-style help lines so hg shows the alias
552 552 self.help = self.help[4 + len(cmd):]
553 553
554 554 doc = cfg.get('doc', pycompat.getdoc(fn))
555 555 if doc is not None:
556 556 doc = pycompat.sysstr(doc)
557 557 self.__doc__ = doc
558 558
559 559 @property
560 560 def args(self):
561 561 args = pycompat.maplist(util.expandpath, self.givenargs)
562 562 return aliasargs(self.fn, args)
563 563
564 564 def __getattr__(self, name):
565 565 adefaults = {r'norepo': True, r'intents': set(),
566 566 r'optionalrepo': False, r'inferrepo': False}
567 567 if name not in adefaults:
568 568 raise AttributeError(name)
569 569 if self.badalias or util.safehasattr(self, 'shell'):
570 570 return adefaults[name]
571 571 return getattr(self.fn, name)
572 572
573 573 def __call__(self, ui, *args, **opts):
574 574 if self.badalias:
575 575 hint = None
576 576 if self.unknowncmd:
577 577 try:
578 578 # check if the command is in a disabled extension
579 579 cmd, ext = extensions.disabledcmd(ui, self.cmdname)[:2]
580 580 hint = _("'%s' is provided by '%s' extension") % (cmd, ext)
581 581 except error.UnknownCommand:
582 582 pass
583 583 raise error.Abort(self.badalias, hint=hint)
584 584 if self.shadows:
585 585 ui.debug("alias '%s' shadows command '%s'\n" %
586 586 (self.name, self.cmdname))
587 587
588 588 ui.log('commandalias', "alias '%s' expands to '%s'\n",
589 589 self.name, self.definition)
590 590 if util.safehasattr(self, 'shell'):
591 591 return self.fn(ui, *args, **opts)
592 592 else:
593 593 try:
594 594 return util.checksignature(self.fn)(ui, *args, **opts)
595 595 except error.SignatureError:
596 596 args = ' '.join([self.cmdname] + self.args)
597 597 ui.debug("alias '%s' expands to '%s'\n" % (self.name, args))
598 598 raise
599 599
600 600 class lazyaliasentry(object):
601 601 """like a typical command entry (func, opts, help), but is lazy"""
602 602
603 603 def __init__(self, ui, name, definition, cmdtable, source):
604 604 self.ui = ui
605 605 self.name = name
606 606 self.definition = definition
607 607 self.cmdtable = cmdtable.copy()
608 608 self.source = source
609 609
610 610 @util.propertycache
611 611 def _aliasdef(self):
612 612 return cmdalias(self.ui, self.name, self.definition, self.cmdtable,
613 613 self.source)
614 614
615 615 def __getitem__(self, n):
616 616 aliasdef = self._aliasdef
617 617 if n == 0:
618 618 return aliasdef
619 619 elif n == 1:
620 620 return aliasdef.opts
621 621 elif n == 2:
622 622 return aliasdef.help
623 623 else:
624 624 raise IndexError
625 625
626 626 def __iter__(self):
627 627 for i in range(3):
628 628 yield self[i]
629 629
630 630 def __len__(self):
631 631 return 3
632 632
633 633 def addaliases(ui, cmdtable):
634 634 # aliases are processed after extensions have been loaded, so they
635 635 # may use extension commands. Aliases can also use other alias definitions,
636 636 # but only if they have been defined prior to the current definition.
637 637 for alias, definition in ui.configitems('alias', ignoresub=True):
638 638 try:
639 639 if cmdtable[alias].definition == definition:
640 640 continue
641 641 except (KeyError, AttributeError):
642 642 # definition might not exist or it might not be a cmdalias
643 643 pass
644 644
645 645 source = ui.configsource('alias', alias)
646 646 entry = lazyaliasentry(ui, alias, definition, cmdtable, source)
647 647 cmdtable[alias] = entry
648 648
649 649 def _parse(ui, args):
650 650 options = {}
651 651 cmdoptions = {}
652 652
653 653 try:
654 654 args = fancyopts.fancyopts(args, commands.globalopts, options)
655 655 except getopt.GetoptError as inst:
656 656 raise error.CommandError(None, stringutil.forcebytestr(inst))
657 657
658 658 if args:
659 659 cmd, args = args[0], args[1:]
660 660 aliases, entry = cmdutil.findcmd(cmd, commands.table,
661 661 ui.configbool("ui", "strict"))
662 662 cmd = aliases[0]
663 663 args = aliasargs(entry[0], args)
664 664 defaults = ui.config("defaults", cmd)
665 665 if defaults:
666 666 args = pycompat.maplist(
667 667 util.expandpath, pycompat.shlexsplit(defaults)) + args
668 668 c = list(entry[1])
669 669 else:
670 670 cmd = None
671 671 c = []
672 672
673 673 # combine global options into local
674 674 for o in commands.globalopts:
675 675 c.append((o[0], o[1], options[o[1]], o[3]))
676 676
677 677 try:
678 678 args = fancyopts.fancyopts(args, c, cmdoptions, gnu=True)
679 679 except getopt.GetoptError as inst:
680 680 raise error.CommandError(cmd, stringutil.forcebytestr(inst))
681 681
682 682 # separate global options back out
683 683 for o in commands.globalopts:
684 684 n = o[1]
685 685 options[n] = cmdoptions[n]
686 686 del cmdoptions[n]
687 687
688 688 return (cmd, cmd and entry[0] or None, args, options, cmdoptions)
689 689
690 690 def _parseconfig(ui, config):
691 691 """parse the --config options from the command line"""
692 692 configs = []
693 693
694 694 for cfg in config:
695 695 try:
696 696 name, value = [cfgelem.strip()
697 697 for cfgelem in cfg.split('=', 1)]
698 698 section, name = name.split('.', 1)
699 699 if not section or not name:
700 700 raise IndexError
701 701 ui.setconfig(section, name, value, '--config')
702 702 configs.append((section, name, value))
703 703 except (IndexError, ValueError):
704 704 raise error.Abort(_('malformed --config option: %r '
705 705 '(use --config section.name=value)')
706 706 % pycompat.bytestr(cfg))
707 707
708 708 return configs
709 709
710 710 def _earlyparseopts(ui, args):
711 711 options = {}
712 712 fancyopts.fancyopts(args, commands.globalopts, options,
713 713 gnu=not ui.plain('strictflags'), early=True,
714 714 optaliases={'repository': ['repo']})
715 715 return options
716 716
717 717 def _earlysplitopts(args):
718 718 """Split args into a list of possible early options and remainder args"""
719 719 shortoptions = 'R:'
720 720 # TODO: perhaps 'debugger' should be included
721 721 longoptions = ['cwd=', 'repository=', 'repo=', 'config=']
722 722 return fancyopts.earlygetopt(args, shortoptions, longoptions,
723 723 gnu=True, keepsep=True)
724 724
725 725 def runcommand(lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions):
726 726 # run pre-hook, and abort if it fails
727 727 hook.hook(lui, repo, "pre-%s" % cmd, True, args=" ".join(fullargs),
728 728 pats=cmdpats, opts=cmdoptions)
729 729 try:
730 730 ret = _runcommand(ui, options, cmd, d)
731 731 # run post-hook, passing command result
732 732 hook.hook(lui, repo, "post-%s" % cmd, False, args=" ".join(fullargs),
733 733 result=ret, pats=cmdpats, opts=cmdoptions)
734 734 except Exception:
735 735 # run failure hook and re-raise
736 736 hook.hook(lui, repo, "fail-%s" % cmd, False, args=" ".join(fullargs),
737 737 pats=cmdpats, opts=cmdoptions)
738 738 raise
739 739 return ret
740 740
741 741 def _getlocal(ui, rpath, wd=None):
742 742 """Return (path, local ui object) for the given target path.
743 743
744 744 Takes paths in [cwd]/.hg/hgrc into account."
745 745 """
746 746 if wd is None:
747 747 try:
748 748 wd = pycompat.getcwd()
749 749 except OSError as e:
750 750 raise error.Abort(_("error getting current working directory: %s") %
751 751 encoding.strtolocal(e.strerror))
752 752 path = cmdutil.findrepo(wd) or ""
753 753 if not path:
754 754 lui = ui
755 755 else:
756 756 lui = ui.copy()
757 757 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
758 758
759 759 if rpath:
760 760 path = lui.expandpath(rpath)
761 761 lui = ui.copy()
762 762 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
763 763
764 764 return path, lui
765 765
766 766 def _checkshellalias(lui, ui, args):
767 767 """Return the function to run the shell alias, if it is required"""
768 768 options = {}
769 769
770 770 try:
771 771 args = fancyopts.fancyopts(args, commands.globalopts, options)
772 772 except getopt.GetoptError:
773 773 return
774 774
775 775 if not args:
776 776 return
777 777
778 778 cmdtable = commands.table
779 779
780 780 cmd = args[0]
781 781 try:
782 782 strict = ui.configbool("ui", "strict")
783 783 aliases, entry = cmdutil.findcmd(cmd, cmdtable, strict)
784 784 except (error.AmbiguousCommand, error.UnknownCommand):
785 785 return
786 786
787 787 cmd = aliases[0]
788 788 fn = entry[0]
789 789
790 790 if cmd and util.safehasattr(fn, 'shell'):
791 791 # shell alias shouldn't receive early options which are consumed by hg
792 792 _earlyopts, args = _earlysplitopts(args)
793 793 d = lambda: fn(ui, *args[1:])
794 794 return lambda: runcommand(lui, None, cmd, args[:1], ui, options, d,
795 795 [], {})
796 796
797 797 def _dispatch(req):
798 798 args = req.args
799 799 ui = req.ui
800 800
801 801 # check for cwd
802 802 cwd = req.earlyoptions['cwd']
803 803 if cwd:
804 804 os.chdir(cwd)
805 805
806 806 rpath = req.earlyoptions['repository']
807 807 path, lui = _getlocal(ui, rpath)
808 808
809 809 uis = {ui, lui}
810 810
811 811 if req.repo:
812 812 uis.add(req.repo.ui)
813 813
814 814 if (req.earlyoptions['verbose'] or req.earlyoptions['debug']
815 815 or req.earlyoptions['quiet']):
816 816 for opt in ('verbose', 'debug', 'quiet'):
817 817 val = pycompat.bytestr(bool(req.earlyoptions[opt]))
818 818 for ui_ in uis:
819 819 ui_.setconfig('ui', opt, val, '--' + opt)
820 820
821 821 if req.earlyoptions['profile']:
822 822 for ui_ in uis:
823 823 ui_.setconfig('profiling', 'enabled', 'true', '--profile')
824 824
825 825 profile = lui.configbool('profiling', 'enabled')
826 826 with profiling.profile(lui, enabled=profile) as profiler:
827 827 # Configure extensions in phases: uisetup, extsetup, cmdtable, and
828 828 # reposetup
829 829 extensions.loadall(lui)
830 830 # Propagate any changes to lui.__class__ by extensions
831 831 ui.__class__ = lui.__class__
832 832
833 833 # (uisetup and extsetup are handled in extensions.loadall)
834 834
835 835 # (reposetup is handled in hg.repository)
836 836
837 837 addaliases(lui, commands.table)
838 838
839 839 # All aliases and commands are completely defined, now.
840 840 # Check abbreviation/ambiguity of shell alias.
841 841 shellaliasfn = _checkshellalias(lui, ui, args)
842 842 if shellaliasfn:
843 843 return shellaliasfn()
844 844
845 845 # check for fallback encoding
846 846 fallback = lui.config('ui', 'fallbackencoding')
847 847 if fallback:
848 848 encoding.fallbackencoding = fallback
849 849
850 850 fullargs = args
851 851 cmd, func, args, options, cmdoptions = _parse(lui, args)
852 852
853 853 if options["config"] != req.earlyoptions["config"]:
854 854 raise error.Abort(_("option --config may not be abbreviated!"))
855 855 if options["cwd"] != req.earlyoptions["cwd"]:
856 856 raise error.Abort(_("option --cwd may not be abbreviated!"))
857 857 if options["repository"] != req.earlyoptions["repository"]:
858 858 raise error.Abort(_(
859 859 "option -R has to be separated from other options (e.g. not "
860 860 "-qR) and --repository may only be abbreviated as --repo!"))
861 861 if options["debugger"] != req.earlyoptions["debugger"]:
862 862 raise error.Abort(_("option --debugger may not be abbreviated!"))
863 863 # don't validate --profile/--traceback, which can be enabled from now
864 864
865 865 if options["encoding"]:
866 866 encoding.encoding = options["encoding"]
867 867 if options["encodingmode"]:
868 868 encoding.encodingmode = options["encodingmode"]
869 869 if options["time"]:
870 870 def get_times():
871 871 t = os.times()
872 872 if t[4] == 0.0:
873 873 # Windows leaves this as zero, so use time.clock()
874 874 t = (t[0], t[1], t[2], t[3], time.clock())
875 875 return t
876 876 s = get_times()
877 877 def print_time():
878 878 t = get_times()
879 879 ui.warn(
880 880 _("time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
881 881 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
882 882 ui.atexit(print_time)
883 883 if options["profile"]:
884 884 profiler.start()
885 885
886 886 # if abbreviated version of this were used, take them in account, now
887 887 if options['verbose'] or options['debug'] or options['quiet']:
888 888 for opt in ('verbose', 'debug', 'quiet'):
889 889 if options[opt] == req.earlyoptions[opt]:
890 890 continue
891 891 val = pycompat.bytestr(bool(options[opt]))
892 892 for ui_ in uis:
893 893 ui_.setconfig('ui', opt, val, '--' + opt)
894 894
895 895 if options['traceback']:
896 896 for ui_ in uis:
897 897 ui_.setconfig('ui', 'traceback', 'on', '--traceback')
898 898
899 899 if options['noninteractive']:
900 900 for ui_ in uis:
901 901 ui_.setconfig('ui', 'interactive', 'off', '-y')
902 902
903 903 if cmdoptions.get('insecure', False):
904 904 for ui_ in uis:
905 905 ui_.insecureconnections = True
906 906
907 907 # setup color handling before pager, because setting up pager
908 908 # might cause incorrect console information
909 909 coloropt = options['color']
910 910 for ui_ in uis:
911 911 if coloropt:
912 912 ui_.setconfig('ui', 'color', coloropt, '--color')
913 913 color.setup(ui_)
914 914
915 915 if stringutil.parsebool(options['pager']):
916 916 # ui.pager() expects 'internal-always-' prefix in this case
917 917 ui.pager('internal-always-' + cmd)
918 918 elif options['pager'] != 'auto':
919 919 for ui_ in uis:
920 920 ui_.disablepager()
921 921
922 922 if options['version']:
923 923 return commands.version_(ui)
924 924 if options['help']:
925 925 return commands.help_(ui, cmd, command=cmd is not None)
926 926 elif not cmd:
927 927 return commands.help_(ui, 'shortlist')
928 928
929 929 repo = None
930 930 cmdpats = args[:]
931 931 if not func.norepo:
932 932 # use the repo from the request only if we don't have -R
933 933 if not rpath and not cwd:
934 934 repo = req.repo
935 935
936 936 if repo:
937 937 # set the descriptors of the repo ui to those of ui
938 938 repo.ui.fin = ui.fin
939 939 repo.ui.fout = ui.fout
940 940 repo.ui.ferr = ui.ferr
941 941 else:
942 942 try:
943 943 repo = hg.repository(ui, path=path,
944 944 presetupfuncs=req.prereposetups,
945 945 intents=func.intents)
946 946 if not repo.local():
947 947 raise error.Abort(_("repository '%s' is not local")
948 948 % path)
949 949 repo.ui.setconfig("bundle", "mainreporoot", repo.root,
950 950 'repo')
951 951 except error.RequirementError:
952 952 raise
953 953 except error.RepoError:
954 954 if rpath: # invalid -R path
955 955 raise
956 956 if not func.optionalrepo:
957 957 if func.inferrepo and args and not path:
958 958 # try to infer -R from command args
959 959 repos = pycompat.maplist(cmdutil.findrepo, args)
960 960 guess = repos[0]
961 961 if guess and repos.count(guess) == len(repos):
962 962 req.args = ['--repository', guess] + fullargs
963 963 req.earlyoptions['repository'] = guess
964 964 return _dispatch(req)
965 965 if not path:
966 966 raise error.RepoError(_("no repository found in"
967 967 " '%s' (.hg not found)")
968 968 % pycompat.getcwd())
969 969 raise
970 970 if repo:
971 971 ui = repo.ui
972 972 if options['hidden']:
973 973 repo = repo.unfiltered()
974 974 args.insert(0, repo)
975 975 elif rpath:
976 976 ui.warn(_("warning: --repository ignored\n"))
977 977
978 978 msg = _formatargs(fullargs)
979 979 ui.log("command", '%s\n', msg)
980 980 strcmdopt = pycompat.strkwargs(cmdoptions)
981 981 d = lambda: util.checksignature(func)(ui, *args, **strcmdopt)
982 982 try:
983 983 return runcommand(lui, repo, cmd, fullargs, ui, options, d,
984 984 cmdpats, cmdoptions)
985 985 finally:
986 986 if repo and repo != req.repo:
987 987 repo.close()
988 988
989 989 def _runcommand(ui, options, cmd, cmdfunc):
990 990 """Run a command function, possibly with profiling enabled."""
991 991 try:
992 992 return cmdfunc()
993 993 except error.SignatureError:
994 994 raise error.CommandError(cmd, _('invalid arguments'))
995 995
996 996 def _exceptionwarning(ui):
997 997 """Produce a warning message for the current active exception"""
998 998
999 999 # For compatibility checking, we discard the portion of the hg
1000 1000 # version after the + on the assumption that if a "normal
1001 1001 # user" is running a build with a + in it the packager
1002 1002 # probably built from fairly close to a tag and anyone with a
1003 1003 # 'make local' copy of hg (where the version number can be out
1004 1004 # of date) will be clueful enough to notice the implausible
1005 1005 # version number and try updating.
1006 1006 ct = util.versiontuple(n=2)
1007 1007 worst = None, ct, ''
1008 1008 if ui.config('ui', 'supportcontact') is None:
1009 1009 for name, mod in extensions.extensions():
1010 1010 # 'testedwith' should be bytes, but not all extensions are ported
1011 1011 # to py3 and we don't want UnicodeException because of that.
1012 1012 testedwith = stringutil.forcebytestr(getattr(mod, 'testedwith', ''))
1013 1013 report = getattr(mod, 'buglink', _('the extension author.'))
1014 1014 if not testedwith.strip():
1015 1015 # We found an untested extension. It's likely the culprit.
1016 1016 worst = name, 'unknown', report
1017 1017 break
1018 1018
1019 1019 # Never blame on extensions bundled with Mercurial.
1020 1020 if extensions.ismoduleinternal(mod):
1021 1021 continue
1022 1022
1023 1023 tested = [util.versiontuple(t, 2) for t in testedwith.split()]
1024 1024 if ct in tested:
1025 1025 continue
1026 1026
1027 1027 lower = [t for t in tested if t < ct]
1028 1028 nearest = max(lower or tested)
1029 1029 if worst[0] is None or nearest < worst[1]:
1030 1030 worst = name, nearest, report
1031 1031 if worst[0] is not None:
1032 1032 name, testedwith, report = worst
1033 1033 if not isinstance(testedwith, (bytes, str)):
1034 1034 testedwith = '.'.join([stringutil.forcebytestr(c)
1035 1035 for c in testedwith])
1036 1036 warning = (_('** Unknown exception encountered with '
1037 1037 'possibly-broken third-party extension %s\n'
1038 1038 '** which supports versions %s of Mercurial.\n'
1039 1039 '** Please disable %s and try your action again.\n'
1040 1040 '** If that fixes the bug please report it to %s\n')
1041 1041 % (name, testedwith, name, stringutil.forcebytestr(report)))
1042 1042 else:
1043 1043 bugtracker = ui.config('ui', 'supportcontact')
1044 1044 if bugtracker is None:
1045 1045 bugtracker = _("https://mercurial-scm.org/wiki/BugTracker")
1046 1046 warning = (_("** unknown exception encountered, "
1047 1047 "please report by visiting\n** ") + bugtracker + '\n')
1048 1048 sysversion = pycompat.sysbytes(sys.version).replace('\n', '')
1049 1049 warning += ((_("** Python %s\n") % sysversion) +
1050 1050 (_("** Mercurial Distributed SCM (version %s)\n") %
1051 1051 util.version()) +
1052 1052 (_("** Extensions loaded: %s\n") %
1053 1053 ", ".join([x[0] for x in extensions.extensions()])))
1054 1054 return warning
1055 1055
1056 1056 def handlecommandexception(ui):
1057 1057 """Produce a warning message for broken commands
1058 1058
1059 1059 Called when handling an exception; the exception is reraised if
1060 1060 this function returns False, ignored otherwise.
1061 1061 """
1062 1062 warning = _exceptionwarning(ui)
1063 1063 ui.log("commandexception", "%s\n%s\n", warning,
1064 1064 pycompat.sysbytes(traceback.format_exc()))
1065 1065 ui.warn(warning)
1066 1066 return False # re-raise the exception
@@ -1,1700 +1,1700 b''
1 1 # scmutil.py - Mercurial core utility functions
2 2 #
3 3 # Copyright Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import errno
11 11 import glob
12 12 import hashlib
13 13 import os
14 14 import re
15 15 import socket
16 16 import subprocess
17 17 import weakref
18 18
19 19 from .i18n import _
20 20 from .node import (
21 21 bin,
22 22 hex,
23 23 nullid,
24 24 short,
25 25 wdirid,
26 26 wdirrev,
27 27 )
28 28
29 29 from . import (
30 30 encoding,
31 31 error,
32 32 match as matchmod,
33 33 obsolete,
34 34 obsutil,
35 35 pathutil,
36 36 phases,
37 37 pycompat,
38 38 revsetlang,
39 39 similar,
40 40 url,
41 41 util,
42 42 vfs,
43 43 )
44 44
45 45 from .utils import (
46 46 procutil,
47 47 stringutil,
48 48 )
49 49
50 50 if pycompat.iswindows:
51 51 from . import scmwindows as scmplatform
52 52 else:
53 53 from . import scmposix as scmplatform
54 54
55 55 termsize = scmplatform.termsize
56 56
57 57 class status(tuple):
58 58 '''Named tuple with a list of files per status. The 'deleted', 'unknown'
59 59 and 'ignored' properties are only relevant to the working copy.
60 60 '''
61 61
62 62 __slots__ = ()
63 63
64 64 def __new__(cls, modified, added, removed, deleted, unknown, ignored,
65 65 clean):
66 66 return tuple.__new__(cls, (modified, added, removed, deleted, unknown,
67 67 ignored, clean))
68 68
69 69 @property
70 70 def modified(self):
71 71 '''files that have been modified'''
72 72 return self[0]
73 73
74 74 @property
75 75 def added(self):
76 76 '''files that have been added'''
77 77 return self[1]
78 78
79 79 @property
80 80 def removed(self):
81 81 '''files that have been removed'''
82 82 return self[2]
83 83
84 84 @property
85 85 def deleted(self):
86 86 '''files that are in the dirstate, but have been deleted from the
87 87 working copy (aka "missing")
88 88 '''
89 89 return self[3]
90 90
91 91 @property
92 92 def unknown(self):
93 93 '''files not in the dirstate that are not ignored'''
94 94 return self[4]
95 95
96 96 @property
97 97 def ignored(self):
98 98 '''files not in the dirstate that are ignored (by _dirignore())'''
99 99 return self[5]
100 100
101 101 @property
102 102 def clean(self):
103 103 '''files that have not been modified'''
104 104 return self[6]
105 105
106 106 def __repr__(self, *args, **kwargs):
107 107 return ((r'<status modified=%s, added=%s, removed=%s, deleted=%s, '
108 108 r'unknown=%s, ignored=%s, clean=%s>') %
109 109 tuple(pycompat.sysstr(stringutil.pprint(v)) for v in self))
110 110
111 111 def itersubrepos(ctx1, ctx2):
112 112 """find subrepos in ctx1 or ctx2"""
113 113 # Create a (subpath, ctx) mapping where we prefer subpaths from
114 114 # ctx1. The subpaths from ctx2 are important when the .hgsub file
115 115 # has been modified (in ctx2) but not yet committed (in ctx1).
116 116 subpaths = dict.fromkeys(ctx2.substate, ctx2)
117 117 subpaths.update(dict.fromkeys(ctx1.substate, ctx1))
118 118
119 119 missing = set()
120 120
121 121 for subpath in ctx2.substate:
122 122 if subpath not in ctx1.substate:
123 123 del subpaths[subpath]
124 124 missing.add(subpath)
125 125
126 126 for subpath, ctx in sorted(subpaths.iteritems()):
127 127 yield subpath, ctx.sub(subpath)
128 128
129 129 # Yield an empty subrepo based on ctx1 for anything only in ctx2. That way,
130 130 # status and diff will have an accurate result when it does
131 131 # 'sub.{status|diff}(rev2)'. Otherwise, the ctx2 subrepo is compared
132 132 # against itself.
133 133 for subpath in missing:
134 134 yield subpath, ctx2.nullsub(subpath, ctx1)
135 135
136 136 def nochangesfound(ui, repo, excluded=None):
137 137 '''Report no changes for push/pull, excluded is None or a list of
138 138 nodes excluded from the push/pull.
139 139 '''
140 140 secretlist = []
141 141 if excluded:
142 142 for n in excluded:
143 143 ctx = repo[n]
144 144 if ctx.phase() >= phases.secret and not ctx.extinct():
145 145 secretlist.append(n)
146 146
147 147 if secretlist:
148 148 ui.status(_("no changes found (ignored %d secret changesets)\n")
149 149 % len(secretlist))
150 150 else:
151 151 ui.status(_("no changes found\n"))
152 152
153 153 def callcatch(ui, func):
154 154 """call func() with global exception handling
155 155
156 156 return func() if no exception happens. otherwise do some error handling
157 157 and return an exit code accordingly. does not handle all exceptions.
158 158 """
159 159 try:
160 160 try:
161 161 return func()
162 162 except: # re-raises
163 163 ui.traceback()
164 164 raise
165 165 # Global exception handling, alphabetically
166 166 # Mercurial-specific first, followed by built-in and library exceptions
167 167 except error.LockHeld as inst:
168 168 if inst.errno == errno.ETIMEDOUT:
169 169 reason = _('timed out waiting for lock held by %r') % inst.locker
170 170 else:
171 171 reason = _('lock held by %r') % inst.locker
172 ui.warn(_("abort: %s: %s\n")
173 % (inst.desc or stringutil.forcebytestr(inst.filename), reason))
172 ui.error(_("abort: %s: %s\n") % (
173 inst.desc or stringutil.forcebytestr(inst.filename), reason))
174 174 if not inst.locker:
175 ui.warn(_("(lock might be very busy)\n"))
175 ui.error(_("(lock might be very busy)\n"))
176 176 except error.LockUnavailable as inst:
177 ui.warn(_("abort: could not lock %s: %s\n") %
178 (inst.desc or stringutil.forcebytestr(inst.filename),
179 encoding.strtolocal(inst.strerror)))
177 ui.error(_("abort: could not lock %s: %s\n") %
178 (inst.desc or stringutil.forcebytestr(inst.filename),
179 encoding.strtolocal(inst.strerror)))
180 180 except error.OutOfBandError as inst:
181 181 if inst.args:
182 182 msg = _("abort: remote error:\n")
183 183 else:
184 184 msg = _("abort: remote error\n")
185 ui.warn(msg)
185 ui.error(msg)
186 186 if inst.args:
187 ui.warn(''.join(inst.args))
187 ui.error(''.join(inst.args))
188 188 if inst.hint:
189 ui.warn('(%s)\n' % inst.hint)
189 ui.error('(%s)\n' % inst.hint)
190 190 except error.RepoError as inst:
191 ui.warn(_("abort: %s!\n") % inst)
191 ui.error(_("abort: %s!\n") % inst)
192 192 if inst.hint:
193 ui.warn(_("(%s)\n") % inst.hint)
193 ui.error(_("(%s)\n") % inst.hint)
194 194 except error.ResponseError as inst:
195 ui.warn(_("abort: %s") % inst.args[0])
195 ui.error(_("abort: %s") % inst.args[0])
196 196 msg = inst.args[1]
197 197 if isinstance(msg, type(u'')):
198 198 msg = pycompat.sysbytes(msg)
199 199 if not isinstance(msg, bytes):
200 ui.warn(" %r\n" % (msg,))
200 ui.error(" %r\n" % (msg,))
201 201 elif not msg:
202 ui.warn(_(" empty string\n"))
202 ui.error(_(" empty string\n"))
203 203 else:
204 ui.warn("\n%r\n" % pycompat.bytestr(stringutil.ellipsis(msg)))
204 ui.error("\n%r\n" % pycompat.bytestr(stringutil.ellipsis(msg)))
205 205 except error.CensoredNodeError as inst:
206 ui.warn(_("abort: file censored %s!\n") % inst)
206 ui.error(_("abort: file censored %s!\n") % inst)
207 207 except error.RevlogError as inst:
208 ui.warn(_("abort: %s!\n") % inst)
208 ui.error(_("abort: %s!\n") % inst)
209 209 except error.InterventionRequired as inst:
210 ui.warn("%s\n" % inst)
210 ui.error("%s\n" % inst)
211 211 if inst.hint:
212 ui.warn(_("(%s)\n") % inst.hint)
212 ui.error(_("(%s)\n") % inst.hint)
213 213 return 1
214 214 except error.WdirUnsupported:
215 ui.warn(_("abort: working directory revision cannot be specified\n"))
215 ui.error(_("abort: working directory revision cannot be specified\n"))
216 216 except error.Abort as inst:
217 ui.warn(_("abort: %s\n") % inst)
217 ui.error(_("abort: %s\n") % inst)
218 218 if inst.hint:
219 ui.warn(_("(%s)\n") % inst.hint)
219 ui.error(_("(%s)\n") % inst.hint)
220 220 except ImportError as inst:
221 ui.warn(_("abort: %s!\n") % stringutil.forcebytestr(inst))
221 ui.error(_("abort: %s!\n") % stringutil.forcebytestr(inst))
222 222 m = stringutil.forcebytestr(inst).split()[-1]
223 223 if m in "mpatch bdiff".split():
224 ui.warn(_("(did you forget to compile extensions?)\n"))
224 ui.error(_("(did you forget to compile extensions?)\n"))
225 225 elif m in "zlib".split():
226 ui.warn(_("(is your Python install correct?)\n"))
226 ui.error(_("(is your Python install correct?)\n"))
227 227 except IOError as inst:
228 228 if util.safehasattr(inst, "code"):
229 ui.warn(_("abort: %s\n") % stringutil.forcebytestr(inst))
229 ui.error(_("abort: %s\n") % stringutil.forcebytestr(inst))
230 230 elif util.safehasattr(inst, "reason"):
231 231 try: # usually it is in the form (errno, strerror)
232 232 reason = inst.reason.args[1]
233 233 except (AttributeError, IndexError):
234 234 # it might be anything, for example a string
235 235 reason = inst.reason
236 236 if isinstance(reason, pycompat.unicode):
237 237 # SSLError of Python 2.7.9 contains a unicode
238 238 reason = encoding.unitolocal(reason)
239 ui.warn(_("abort: error: %s\n") % reason)
239 ui.error(_("abort: error: %s\n") % reason)
240 240 elif (util.safehasattr(inst, "args")
241 241 and inst.args and inst.args[0] == errno.EPIPE):
242 242 pass
243 243 elif getattr(inst, "strerror", None):
244 244 if getattr(inst, "filename", None):
245 ui.warn(_("abort: %s: %s\n") % (
245 ui.error(_("abort: %s: %s\n") % (
246 246 encoding.strtolocal(inst.strerror),
247 247 stringutil.forcebytestr(inst.filename)))
248 248 else:
249 ui.warn(_("abort: %s\n") % encoding.strtolocal(inst.strerror))
249 ui.error(_("abort: %s\n") % encoding.strtolocal(inst.strerror))
250 250 else:
251 251 raise
252 252 except OSError as inst:
253 253 if getattr(inst, "filename", None) is not None:
254 ui.warn(_("abort: %s: '%s'\n") % (
254 ui.error(_("abort: %s: '%s'\n") % (
255 255 encoding.strtolocal(inst.strerror),
256 256 stringutil.forcebytestr(inst.filename)))
257 257 else:
258 ui.warn(_("abort: %s\n") % encoding.strtolocal(inst.strerror))
258 ui.error(_("abort: %s\n") % encoding.strtolocal(inst.strerror))
259 259 except MemoryError:
260 ui.warn(_("abort: out of memory\n"))
260 ui.error(_("abort: out of memory\n"))
261 261 except SystemExit as inst:
262 262 # Commands shouldn't sys.exit directly, but give a return code.
263 263 # Just in case catch this and and pass exit code to caller.
264 264 return inst.code
265 265 except socket.error as inst:
266 ui.warn(_("abort: %s\n") % stringutil.forcebytestr(inst.args[-1]))
266 ui.error(_("abort: %s\n") % stringutil.forcebytestr(inst.args[-1]))
267 267
268 268 return -1
269 269
270 270 def checknewlabel(repo, lbl, kind):
271 271 # Do not use the "kind" parameter in ui output.
272 272 # It makes strings difficult to translate.
273 273 if lbl in ['tip', '.', 'null']:
274 274 raise error.Abort(_("the name '%s' is reserved") % lbl)
275 275 for c in (':', '\0', '\n', '\r'):
276 276 if c in lbl:
277 277 raise error.Abort(
278 278 _("%r cannot be used in a name") % pycompat.bytestr(c))
279 279 try:
280 280 int(lbl)
281 281 raise error.Abort(_("cannot use an integer as a name"))
282 282 except ValueError:
283 283 pass
284 284 if lbl.strip() != lbl:
285 285 raise error.Abort(_("leading or trailing whitespace in name %r") % lbl)
286 286
287 287 def checkfilename(f):
288 288 '''Check that the filename f is an acceptable filename for a tracked file'''
289 289 if '\r' in f or '\n' in f:
290 290 raise error.Abort(_("'\\n' and '\\r' disallowed in filenames: %r")
291 291 % pycompat.bytestr(f))
292 292
293 293 def checkportable(ui, f):
294 294 '''Check if filename f is portable and warn or abort depending on config'''
295 295 checkfilename(f)
296 296 abort, warn = checkportabilityalert(ui)
297 297 if abort or warn:
298 298 msg = util.checkwinfilename(f)
299 299 if msg:
300 300 msg = "%s: %s" % (msg, procutil.shellquote(f))
301 301 if abort:
302 302 raise error.Abort(msg)
303 303 ui.warn(_("warning: %s\n") % msg)
304 304
305 305 def checkportabilityalert(ui):
306 306 '''check if the user's config requests nothing, a warning, or abort for
307 307 non-portable filenames'''
308 308 val = ui.config('ui', 'portablefilenames')
309 309 lval = val.lower()
310 310 bval = stringutil.parsebool(val)
311 311 abort = pycompat.iswindows or lval == 'abort'
312 312 warn = bval or lval == 'warn'
313 313 if bval is None and not (warn or abort or lval == 'ignore'):
314 314 raise error.ConfigError(
315 315 _("ui.portablefilenames value is invalid ('%s')") % val)
316 316 return abort, warn
317 317
318 318 class casecollisionauditor(object):
319 319 def __init__(self, ui, abort, dirstate):
320 320 self._ui = ui
321 321 self._abort = abort
322 322 allfiles = '\0'.join(dirstate._map)
323 323 self._loweredfiles = set(encoding.lower(allfiles).split('\0'))
324 324 self._dirstate = dirstate
325 325 # The purpose of _newfiles is so that we don't complain about
326 326 # case collisions if someone were to call this object with the
327 327 # same filename twice.
328 328 self._newfiles = set()
329 329
330 330 def __call__(self, f):
331 331 if f in self._newfiles:
332 332 return
333 333 fl = encoding.lower(f)
334 334 if fl in self._loweredfiles and f not in self._dirstate:
335 335 msg = _('possible case-folding collision for %s') % f
336 336 if self._abort:
337 337 raise error.Abort(msg)
338 338 self._ui.warn(_("warning: %s\n") % msg)
339 339 self._loweredfiles.add(fl)
340 340 self._newfiles.add(f)
341 341
342 342 def filteredhash(repo, maxrev):
343 343 """build hash of filtered revisions in the current repoview.
344 344
345 345 Multiple caches perform up-to-date validation by checking that the
346 346 tiprev and tipnode stored in the cache file match the current repository.
347 347 However, this is not sufficient for validating repoviews because the set
348 348 of revisions in the view may change without the repository tiprev and
349 349 tipnode changing.
350 350
351 351 This function hashes all the revs filtered from the view and returns
352 352 that SHA-1 digest.
353 353 """
354 354 cl = repo.changelog
355 355 if not cl.filteredrevs:
356 356 return None
357 357 key = None
358 358 revs = sorted(r for r in cl.filteredrevs if r <= maxrev)
359 359 if revs:
360 360 s = hashlib.sha1()
361 361 for rev in revs:
362 362 s.update('%d;' % rev)
363 363 key = s.digest()
364 364 return key
365 365
366 366 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
367 367 '''yield every hg repository under path, always recursively.
368 368 The recurse flag will only control recursion into repo working dirs'''
369 369 def errhandler(err):
370 370 if err.filename == path:
371 371 raise err
372 372 samestat = getattr(os.path, 'samestat', None)
373 373 if followsym and samestat is not None:
374 374 def adddir(dirlst, dirname):
375 375 dirstat = os.stat(dirname)
376 376 match = any(samestat(dirstat, lstdirstat) for lstdirstat in dirlst)
377 377 if not match:
378 378 dirlst.append(dirstat)
379 379 return not match
380 380 else:
381 381 followsym = False
382 382
383 383 if (seen_dirs is None) and followsym:
384 384 seen_dirs = []
385 385 adddir(seen_dirs, path)
386 386 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
387 387 dirs.sort()
388 388 if '.hg' in dirs:
389 389 yield root # found a repository
390 390 qroot = os.path.join(root, '.hg', 'patches')
391 391 if os.path.isdir(os.path.join(qroot, '.hg')):
392 392 yield qroot # we have a patch queue repo here
393 393 if recurse:
394 394 # avoid recursing inside the .hg directory
395 395 dirs.remove('.hg')
396 396 else:
397 397 dirs[:] = [] # don't descend further
398 398 elif followsym:
399 399 newdirs = []
400 400 for d in dirs:
401 401 fname = os.path.join(root, d)
402 402 if adddir(seen_dirs, fname):
403 403 if os.path.islink(fname):
404 404 for hgname in walkrepos(fname, True, seen_dirs):
405 405 yield hgname
406 406 else:
407 407 newdirs.append(d)
408 408 dirs[:] = newdirs
409 409
410 410 def binnode(ctx):
411 411 """Return binary node id for a given basectx"""
412 412 node = ctx.node()
413 413 if node is None:
414 414 return wdirid
415 415 return node
416 416
417 417 def intrev(ctx):
418 418 """Return integer for a given basectx that can be used in comparison or
419 419 arithmetic operation"""
420 420 rev = ctx.rev()
421 421 if rev is None:
422 422 return wdirrev
423 423 return rev
424 424
425 425 def formatchangeid(ctx):
426 426 """Format changectx as '{rev}:{node|formatnode}', which is the default
427 427 template provided by logcmdutil.changesettemplater"""
428 428 repo = ctx.repo()
429 429 return formatrevnode(repo.ui, intrev(ctx), binnode(ctx))
430 430
431 431 def formatrevnode(ui, rev, node):
432 432 """Format given revision and node depending on the current verbosity"""
433 433 if ui.debugflag:
434 434 hexfunc = hex
435 435 else:
436 436 hexfunc = short
437 437 return '%d:%s' % (rev, hexfunc(node))
438 438
439 439 def resolvehexnodeidprefix(repo, prefix):
440 440 # Uses unfiltered repo because it's faster when prefix is ambiguous/
441 441 # This matches the shortesthexnodeidprefix() function below.
442 442 node = repo.unfiltered().changelog._partialmatch(prefix)
443 443 if node is None:
444 444 return
445 445 repo.changelog.rev(node) # make sure node isn't filtered
446 446 return node
447 447
448 448 def shortesthexnodeidprefix(repo, node, minlength=1):
449 449 """Find the shortest unambiguous prefix that matches hexnode."""
450 450 # _partialmatch() of filtered changelog could take O(len(repo)) time,
451 451 # which would be unacceptably slow. so we look for hash collision in
452 452 # unfiltered space, which means some hashes may be slightly longer.
453 453 cl = repo.unfiltered().changelog
454 454
455 455 def isrev(prefix):
456 456 try:
457 457 i = int(prefix)
458 458 # if we are a pure int, then starting with zero will not be
459 459 # confused as a rev; or, obviously, if the int is larger
460 460 # than the value of the tip rev
461 461 if prefix[0:1] == b'0' or i > len(cl):
462 462 return False
463 463 return True
464 464 except ValueError:
465 465 return False
466 466
467 467 def disambiguate(prefix):
468 468 """Disambiguate against revnums."""
469 469 hexnode = hex(node)
470 470 for length in range(len(prefix), len(hexnode) + 1):
471 471 prefix = hexnode[:length]
472 472 if not isrev(prefix):
473 473 return prefix
474 474
475 475 try:
476 476 return disambiguate(cl.shortest(node, minlength))
477 477 except error.LookupError:
478 478 raise error.RepoLookupError()
479 479
480 480 def isrevsymbol(repo, symbol):
481 481 """Checks if a symbol exists in the repo.
482 482
483 483 See revsymbol() for details. Raises error.LookupError if the symbol is an
484 484 ambiguous nodeid prefix.
485 485 """
486 486 try:
487 487 revsymbol(repo, symbol)
488 488 return True
489 489 except error.RepoLookupError:
490 490 return False
491 491
492 492 def revsymbol(repo, symbol):
493 493 """Returns a context given a single revision symbol (as string).
494 494
495 495 This is similar to revsingle(), but accepts only a single revision symbol,
496 496 i.e. things like ".", "tip", "1234", "deadbeef", "my-bookmark" work, but
497 497 not "max(public())".
498 498 """
499 499 if not isinstance(symbol, bytes):
500 500 msg = ("symbol (%s of type %s) was not a string, did you mean "
501 501 "repo[symbol]?" % (symbol, type(symbol)))
502 502 raise error.ProgrammingError(msg)
503 503 try:
504 504 if symbol in ('.', 'tip', 'null'):
505 505 return repo[symbol]
506 506
507 507 try:
508 508 r = int(symbol)
509 509 if '%d' % r != symbol:
510 510 raise ValueError
511 511 l = len(repo.changelog)
512 512 if r < 0:
513 513 r += l
514 514 if r < 0 or r >= l and r != wdirrev:
515 515 raise ValueError
516 516 return repo[r]
517 517 except error.FilteredIndexError:
518 518 raise
519 519 except (ValueError, OverflowError, IndexError):
520 520 pass
521 521
522 522 if len(symbol) == 40:
523 523 try:
524 524 node = bin(symbol)
525 525 rev = repo.changelog.rev(node)
526 526 return repo[rev]
527 527 except error.FilteredLookupError:
528 528 raise
529 529 except (TypeError, LookupError):
530 530 pass
531 531
532 532 # look up bookmarks through the name interface
533 533 try:
534 534 node = repo.names.singlenode(repo, symbol)
535 535 rev = repo.changelog.rev(node)
536 536 return repo[rev]
537 537 except KeyError:
538 538 pass
539 539
540 540 node = resolvehexnodeidprefix(repo, symbol)
541 541 if node is not None:
542 542 rev = repo.changelog.rev(node)
543 543 return repo[rev]
544 544
545 545 raise error.RepoLookupError(_("unknown revision '%s'") % symbol)
546 546
547 547 except error.WdirUnsupported:
548 548 return repo[None]
549 549 except (error.FilteredIndexError, error.FilteredLookupError,
550 550 error.FilteredRepoLookupError):
551 551 raise _filterederror(repo, symbol)
552 552
553 553 def _filterederror(repo, changeid):
554 554 """build an exception to be raised about a filtered changeid
555 555
556 556 This is extracted in a function to help extensions (eg: evolve) to
557 557 experiment with various message variants."""
558 558 if repo.filtername.startswith('visible'):
559 559
560 560 # Check if the changeset is obsolete
561 561 unfilteredrepo = repo.unfiltered()
562 562 ctx = revsymbol(unfilteredrepo, changeid)
563 563
564 564 # If the changeset is obsolete, enrich the message with the reason
565 565 # that made this changeset not visible
566 566 if ctx.obsolete():
567 567 msg = obsutil._getfilteredreason(repo, changeid, ctx)
568 568 else:
569 569 msg = _("hidden revision '%s'") % changeid
570 570
571 571 hint = _('use --hidden to access hidden revisions')
572 572
573 573 return error.FilteredRepoLookupError(msg, hint=hint)
574 574 msg = _("filtered revision '%s' (not in '%s' subset)")
575 575 msg %= (changeid, repo.filtername)
576 576 return error.FilteredRepoLookupError(msg)
577 577
578 578 def revsingle(repo, revspec, default='.', localalias=None):
579 579 if not revspec and revspec != 0:
580 580 return repo[default]
581 581
582 582 l = revrange(repo, [revspec], localalias=localalias)
583 583 if not l:
584 584 raise error.Abort(_('empty revision set'))
585 585 return repo[l.last()]
586 586
587 587 def _pairspec(revspec):
588 588 tree = revsetlang.parse(revspec)
589 589 return tree and tree[0] in ('range', 'rangepre', 'rangepost', 'rangeall')
590 590
591 591 def revpair(repo, revs):
592 592 if not revs:
593 593 return repo['.'], repo[None]
594 594
595 595 l = revrange(repo, revs)
596 596
597 597 if not l:
598 598 first = second = None
599 599 elif l.isascending():
600 600 first = l.min()
601 601 second = l.max()
602 602 elif l.isdescending():
603 603 first = l.max()
604 604 second = l.min()
605 605 else:
606 606 first = l.first()
607 607 second = l.last()
608 608
609 609 if first is None:
610 610 raise error.Abort(_('empty revision range'))
611 611 if (first == second and len(revs) >= 2
612 612 and not all(revrange(repo, [r]) for r in revs)):
613 613 raise error.Abort(_('empty revision on one side of range'))
614 614
615 615 # if top-level is range expression, the result must always be a pair
616 616 if first == second and len(revs) == 1 and not _pairspec(revs[0]):
617 617 return repo[first], repo[None]
618 618
619 619 return repo[first], repo[second]
620 620
621 621 def revrange(repo, specs, localalias=None):
622 622 """Execute 1 to many revsets and return the union.
623 623
624 624 This is the preferred mechanism for executing revsets using user-specified
625 625 config options, such as revset aliases.
626 626
627 627 The revsets specified by ``specs`` will be executed via a chained ``OR``
628 628 expression. If ``specs`` is empty, an empty result is returned.
629 629
630 630 ``specs`` can contain integers, in which case they are assumed to be
631 631 revision numbers.
632 632
633 633 It is assumed the revsets are already formatted. If you have arguments
634 634 that need to be expanded in the revset, call ``revsetlang.formatspec()``
635 635 and pass the result as an element of ``specs``.
636 636
637 637 Specifying a single revset is allowed.
638 638
639 639 Returns a ``revset.abstractsmartset`` which is a list-like interface over
640 640 integer revisions.
641 641 """
642 642 allspecs = []
643 643 for spec in specs:
644 644 if isinstance(spec, int):
645 645 spec = revsetlang.formatspec('rev(%d)', spec)
646 646 allspecs.append(spec)
647 647 return repo.anyrevs(allspecs, user=True, localalias=localalias)
648 648
649 649 def meaningfulparents(repo, ctx):
650 650 """Return list of meaningful (or all if debug) parentrevs for rev.
651 651
652 652 For merges (two non-nullrev revisions) both parents are meaningful.
653 653 Otherwise the first parent revision is considered meaningful if it
654 654 is not the preceding revision.
655 655 """
656 656 parents = ctx.parents()
657 657 if len(parents) > 1:
658 658 return parents
659 659 if repo.ui.debugflag:
660 660 return [parents[0], repo['null']]
661 661 if parents[0].rev() >= intrev(ctx) - 1:
662 662 return []
663 663 return parents
664 664
665 665 def expandpats(pats):
666 666 '''Expand bare globs when running on windows.
667 667 On posix we assume it already has already been done by sh.'''
668 668 if not util.expandglobs:
669 669 return list(pats)
670 670 ret = []
671 671 for kindpat in pats:
672 672 kind, pat = matchmod._patsplit(kindpat, None)
673 673 if kind is None:
674 674 try:
675 675 globbed = glob.glob(pat)
676 676 except re.error:
677 677 globbed = [pat]
678 678 if globbed:
679 679 ret.extend(globbed)
680 680 continue
681 681 ret.append(kindpat)
682 682 return ret
683 683
684 684 def matchandpats(ctx, pats=(), opts=None, globbed=False, default='relpath',
685 685 badfn=None):
686 686 '''Return a matcher and the patterns that were used.
687 687 The matcher will warn about bad matches, unless an alternate badfn callback
688 688 is provided.'''
689 689 if pats == ("",):
690 690 pats = []
691 691 if opts is None:
692 692 opts = {}
693 693 if not globbed and default == 'relpath':
694 694 pats = expandpats(pats or [])
695 695
696 696 def bad(f, msg):
697 697 ctx.repo().ui.warn("%s: %s\n" % (m.rel(f), msg))
698 698
699 699 if badfn is None:
700 700 badfn = bad
701 701
702 702 m = ctx.match(pats, opts.get('include'), opts.get('exclude'),
703 703 default, listsubrepos=opts.get('subrepos'), badfn=badfn)
704 704
705 705 if m.always():
706 706 pats = []
707 707 return m, pats
708 708
709 709 def match(ctx, pats=(), opts=None, globbed=False, default='relpath',
710 710 badfn=None):
711 711 '''Return a matcher that will warn about bad matches.'''
712 712 return matchandpats(ctx, pats, opts, globbed, default, badfn=badfn)[0]
713 713
714 714 def matchall(repo):
715 715 '''Return a matcher that will efficiently match everything.'''
716 716 return matchmod.always(repo.root, repo.getcwd())
717 717
718 718 def matchfiles(repo, files, badfn=None):
719 719 '''Return a matcher that will efficiently match exactly these files.'''
720 720 return matchmod.exact(repo.root, repo.getcwd(), files, badfn=badfn)
721 721
722 722 def parsefollowlinespattern(repo, rev, pat, msg):
723 723 """Return a file name from `pat` pattern suitable for usage in followlines
724 724 logic.
725 725 """
726 726 if not matchmod.patkind(pat):
727 727 return pathutil.canonpath(repo.root, repo.getcwd(), pat)
728 728 else:
729 729 ctx = repo[rev]
730 730 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=ctx)
731 731 files = [f for f in ctx if m(f)]
732 732 if len(files) != 1:
733 733 raise error.ParseError(msg)
734 734 return files[0]
735 735
736 736 def origpath(ui, repo, filepath):
737 737 '''customize where .orig files are created
738 738
739 739 Fetch user defined path from config file: [ui] origbackuppath = <path>
740 740 Fall back to default (filepath with .orig suffix) if not specified
741 741 '''
742 742 origbackuppath = ui.config('ui', 'origbackuppath')
743 743 if not origbackuppath:
744 744 return filepath + ".orig"
745 745
746 746 # Convert filepath from an absolute path into a path inside the repo.
747 747 filepathfromroot = util.normpath(os.path.relpath(filepath,
748 748 start=repo.root))
749 749
750 750 origvfs = vfs.vfs(repo.wjoin(origbackuppath))
751 751 origbackupdir = origvfs.dirname(filepathfromroot)
752 752 if not origvfs.isdir(origbackupdir) or origvfs.islink(origbackupdir):
753 753 ui.note(_('creating directory: %s\n') % origvfs.join(origbackupdir))
754 754
755 755 # Remove any files that conflict with the backup file's path
756 756 for f in reversed(list(util.finddirs(filepathfromroot))):
757 757 if origvfs.isfileorlink(f):
758 758 ui.note(_('removing conflicting file: %s\n')
759 759 % origvfs.join(f))
760 760 origvfs.unlink(f)
761 761 break
762 762
763 763 origvfs.makedirs(origbackupdir)
764 764
765 765 if origvfs.isdir(filepathfromroot) and not origvfs.islink(filepathfromroot):
766 766 ui.note(_('removing conflicting directory: %s\n')
767 767 % origvfs.join(filepathfromroot))
768 768 origvfs.rmtree(filepathfromroot, forcibly=True)
769 769
770 770 return origvfs.join(filepathfromroot)
771 771
772 772 class _containsnode(object):
773 773 """proxy __contains__(node) to container.__contains__ which accepts revs"""
774 774
775 775 def __init__(self, repo, revcontainer):
776 776 self._torev = repo.changelog.rev
777 777 self._revcontains = revcontainer.__contains__
778 778
779 779 def __contains__(self, node):
780 780 return self._revcontains(self._torev(node))
781 781
782 782 def cleanupnodes(repo, replacements, operation, moves=None, metadata=None,
783 783 fixphase=False, targetphase=None):
784 784 """do common cleanups when old nodes are replaced by new nodes
785 785
786 786 That includes writing obsmarkers or stripping nodes, and moving bookmarks.
787 787 (we might also want to move working directory parent in the future)
788 788
789 789 By default, bookmark moves are calculated automatically from 'replacements',
790 790 but 'moves' can be used to override that. Also, 'moves' may include
791 791 additional bookmark moves that should not have associated obsmarkers.
792 792
793 793 replacements is {oldnode: [newnode]} or a iterable of nodes if they do not
794 794 have replacements. operation is a string, like "rebase".
795 795
796 796 metadata is dictionary containing metadata to be stored in obsmarker if
797 797 obsolescence is enabled.
798 798 """
799 799 assert fixphase or targetphase is None
800 800 if not replacements and not moves:
801 801 return
802 802
803 803 # translate mapping's other forms
804 804 if not util.safehasattr(replacements, 'items'):
805 805 replacements = {n: () for n in replacements}
806 806
807 807 # Calculate bookmark movements
808 808 if moves is None:
809 809 moves = {}
810 810 # Unfiltered repo is needed since nodes in replacements might be hidden.
811 811 unfi = repo.unfiltered()
812 812 for oldnode, newnodes in replacements.items():
813 813 if oldnode in moves:
814 814 continue
815 815 if len(newnodes) > 1:
816 816 # usually a split, take the one with biggest rev number
817 817 newnode = next(unfi.set('max(%ln)', newnodes)).node()
818 818 elif len(newnodes) == 0:
819 819 # move bookmark backwards
820 820 roots = list(unfi.set('max((::%n) - %ln)', oldnode,
821 821 list(replacements)))
822 822 if roots:
823 823 newnode = roots[0].node()
824 824 else:
825 825 newnode = nullid
826 826 else:
827 827 newnode = newnodes[0]
828 828 moves[oldnode] = newnode
829 829
830 830 allnewnodes = [n for ns in replacements.values() for n in ns]
831 831 toretract = {}
832 832 toadvance = {}
833 833 if fixphase:
834 834 precursors = {}
835 835 for oldnode, newnodes in replacements.items():
836 836 for newnode in newnodes:
837 837 precursors.setdefault(newnode, []).append(oldnode)
838 838
839 839 allnewnodes.sort(key=lambda n: unfi[n].rev())
840 840 newphases = {}
841 841 def phase(ctx):
842 842 return newphases.get(ctx.node(), ctx.phase())
843 843 for newnode in allnewnodes:
844 844 ctx = unfi[newnode]
845 845 parentphase = max(phase(p) for p in ctx.parents())
846 846 if targetphase is None:
847 847 oldphase = max(unfi[oldnode].phase()
848 848 for oldnode in precursors[newnode])
849 849 newphase = max(oldphase, parentphase)
850 850 else:
851 851 newphase = max(targetphase, parentphase)
852 852 newphases[newnode] = newphase
853 853 if newphase > ctx.phase():
854 854 toretract.setdefault(newphase, []).append(newnode)
855 855 elif newphase < ctx.phase():
856 856 toadvance.setdefault(newphase, []).append(newnode)
857 857
858 858 with repo.transaction('cleanup') as tr:
859 859 # Move bookmarks
860 860 bmarks = repo._bookmarks
861 861 bmarkchanges = []
862 862 for oldnode, newnode in moves.items():
863 863 oldbmarks = repo.nodebookmarks(oldnode)
864 864 if not oldbmarks:
865 865 continue
866 866 from . import bookmarks # avoid import cycle
867 867 repo.ui.debug('moving bookmarks %r from %s to %s\n' %
868 868 (pycompat.rapply(pycompat.maybebytestr, oldbmarks),
869 869 hex(oldnode), hex(newnode)))
870 870 # Delete divergent bookmarks being parents of related newnodes
871 871 deleterevs = repo.revs('parents(roots(%ln & (::%n))) - parents(%n)',
872 872 allnewnodes, newnode, oldnode)
873 873 deletenodes = _containsnode(repo, deleterevs)
874 874 for name in oldbmarks:
875 875 bmarkchanges.append((name, newnode))
876 876 for b in bookmarks.divergent2delete(repo, deletenodes, name):
877 877 bmarkchanges.append((b, None))
878 878
879 879 if bmarkchanges:
880 880 bmarks.applychanges(repo, tr, bmarkchanges)
881 881
882 882 for phase, nodes in toretract.items():
883 883 phases.retractboundary(repo, tr, phase, nodes)
884 884 for phase, nodes in toadvance.items():
885 885 phases.advanceboundary(repo, tr, phase, nodes)
886 886
887 887 # Obsolete or strip nodes
888 888 if obsolete.isenabled(repo, obsolete.createmarkersopt):
889 889 # If a node is already obsoleted, and we want to obsolete it
890 890 # without a successor, skip that obssolete request since it's
891 891 # unnecessary. That's the "if s or not isobs(n)" check below.
892 892 # Also sort the node in topology order, that might be useful for
893 893 # some obsstore logic.
894 894 # NOTE: the filtering and sorting might belong to createmarkers.
895 895 isobs = unfi.obsstore.successors.__contains__
896 896 torev = unfi.changelog.rev
897 897 sortfunc = lambda ns: torev(ns[0])
898 898 rels = [(unfi[n], tuple(unfi[m] for m in s))
899 899 for n, s in sorted(replacements.items(), key=sortfunc)
900 900 if s or not isobs(n)]
901 901 if rels:
902 902 obsolete.createmarkers(repo, rels, operation=operation,
903 903 metadata=metadata)
904 904 else:
905 905 from . import repair # avoid import cycle
906 906 tostrip = list(replacements)
907 907 if tostrip:
908 908 repair.delayedstrip(repo.ui, repo, tostrip, operation)
909 909
910 910 def addremove(repo, matcher, prefix, opts=None):
911 911 if opts is None:
912 912 opts = {}
913 913 m = matcher
914 914 dry_run = opts.get('dry_run')
915 915 try:
916 916 similarity = float(opts.get('similarity') or 0)
917 917 except ValueError:
918 918 raise error.Abort(_('similarity must be a number'))
919 919 if similarity < 0 or similarity > 100:
920 920 raise error.Abort(_('similarity must be between 0 and 100'))
921 921 similarity /= 100.0
922 922
923 923 ret = 0
924 924 join = lambda f: os.path.join(prefix, f)
925 925
926 926 wctx = repo[None]
927 927 for subpath in sorted(wctx.substate):
928 928 submatch = matchmod.subdirmatcher(subpath, m)
929 929 if opts.get('subrepos') or m.exact(subpath) or any(submatch.files()):
930 930 sub = wctx.sub(subpath)
931 931 try:
932 932 if sub.addremove(submatch, prefix, opts):
933 933 ret = 1
934 934 except error.LookupError:
935 935 repo.ui.status(_("skipping missing subrepository: %s\n")
936 936 % join(subpath))
937 937
938 938 rejected = []
939 939 def badfn(f, msg):
940 940 if f in m.files():
941 941 m.bad(f, msg)
942 942 rejected.append(f)
943 943
944 944 badmatch = matchmod.badmatch(m, badfn)
945 945 added, unknown, deleted, removed, forgotten = _interestingfiles(repo,
946 946 badmatch)
947 947
948 948 unknownset = set(unknown + forgotten)
949 949 toprint = unknownset.copy()
950 950 toprint.update(deleted)
951 951 for abs in sorted(toprint):
952 952 if repo.ui.verbose or not m.exact(abs):
953 953 if abs in unknownset:
954 954 status = _('adding %s\n') % m.uipath(abs)
955 955 else:
956 956 status = _('removing %s\n') % m.uipath(abs)
957 957 repo.ui.status(status)
958 958
959 959 renames = _findrenames(repo, m, added + unknown, removed + deleted,
960 960 similarity)
961 961
962 962 if not dry_run:
963 963 _markchanges(repo, unknown + forgotten, deleted, renames)
964 964
965 965 for f in rejected:
966 966 if f in m.files():
967 967 return 1
968 968 return ret
969 969
970 970 def marktouched(repo, files, similarity=0.0):
971 971 '''Assert that files have somehow been operated upon. files are relative to
972 972 the repo root.'''
973 973 m = matchfiles(repo, files, badfn=lambda x, y: rejected.append(x))
974 974 rejected = []
975 975
976 976 added, unknown, deleted, removed, forgotten = _interestingfiles(repo, m)
977 977
978 978 if repo.ui.verbose:
979 979 unknownset = set(unknown + forgotten)
980 980 toprint = unknownset.copy()
981 981 toprint.update(deleted)
982 982 for abs in sorted(toprint):
983 983 if abs in unknownset:
984 984 status = _('adding %s\n') % abs
985 985 else:
986 986 status = _('removing %s\n') % abs
987 987 repo.ui.status(status)
988 988
989 989 renames = _findrenames(repo, m, added + unknown, removed + deleted,
990 990 similarity)
991 991
992 992 _markchanges(repo, unknown + forgotten, deleted, renames)
993 993
994 994 for f in rejected:
995 995 if f in m.files():
996 996 return 1
997 997 return 0
998 998
999 999 def _interestingfiles(repo, matcher):
1000 1000 '''Walk dirstate with matcher, looking for files that addremove would care
1001 1001 about.
1002 1002
1003 1003 This is different from dirstate.status because it doesn't care about
1004 1004 whether files are modified or clean.'''
1005 1005 added, unknown, deleted, removed, forgotten = [], [], [], [], []
1006 1006 audit_path = pathutil.pathauditor(repo.root, cached=True)
1007 1007
1008 1008 ctx = repo[None]
1009 1009 dirstate = repo.dirstate
1010 1010 walkresults = dirstate.walk(matcher, subrepos=sorted(ctx.substate),
1011 1011 unknown=True, ignored=False, full=False)
1012 1012 for abs, st in walkresults.iteritems():
1013 1013 dstate = dirstate[abs]
1014 1014 if dstate == '?' and audit_path.check(abs):
1015 1015 unknown.append(abs)
1016 1016 elif dstate != 'r' and not st:
1017 1017 deleted.append(abs)
1018 1018 elif dstate == 'r' and st:
1019 1019 forgotten.append(abs)
1020 1020 # for finding renames
1021 1021 elif dstate == 'r' and not st:
1022 1022 removed.append(abs)
1023 1023 elif dstate == 'a':
1024 1024 added.append(abs)
1025 1025
1026 1026 return added, unknown, deleted, removed, forgotten
1027 1027
1028 1028 def _findrenames(repo, matcher, added, removed, similarity):
1029 1029 '''Find renames from removed files to added ones.'''
1030 1030 renames = {}
1031 1031 if similarity > 0:
1032 1032 for old, new, score in similar.findrenames(repo, added, removed,
1033 1033 similarity):
1034 1034 if (repo.ui.verbose or not matcher.exact(old)
1035 1035 or not matcher.exact(new)):
1036 1036 repo.ui.status(_('recording removal of %s as rename to %s '
1037 1037 '(%d%% similar)\n') %
1038 1038 (matcher.rel(old), matcher.rel(new),
1039 1039 score * 100))
1040 1040 renames[new] = old
1041 1041 return renames
1042 1042
1043 1043 def _markchanges(repo, unknown, deleted, renames):
1044 1044 '''Marks the files in unknown as added, the files in deleted as removed,
1045 1045 and the files in renames as copied.'''
1046 1046 wctx = repo[None]
1047 1047 with repo.wlock():
1048 1048 wctx.forget(deleted)
1049 1049 wctx.add(unknown)
1050 1050 for new, old in renames.iteritems():
1051 1051 wctx.copy(old, new)
1052 1052
1053 1053 def dirstatecopy(ui, repo, wctx, src, dst, dryrun=False, cwd=None):
1054 1054 """Update the dirstate to reflect the intent of copying src to dst. For
1055 1055 different reasons it might not end with dst being marked as copied from src.
1056 1056 """
1057 1057 origsrc = repo.dirstate.copied(src) or src
1058 1058 if dst == origsrc: # copying back a copy?
1059 1059 if repo.dirstate[dst] not in 'mn' and not dryrun:
1060 1060 repo.dirstate.normallookup(dst)
1061 1061 else:
1062 1062 if repo.dirstate[origsrc] == 'a' and origsrc == src:
1063 1063 if not ui.quiet:
1064 1064 ui.warn(_("%s has not been committed yet, so no copy "
1065 1065 "data will be stored for %s.\n")
1066 1066 % (repo.pathto(origsrc, cwd), repo.pathto(dst, cwd)))
1067 1067 if repo.dirstate[dst] in '?r' and not dryrun:
1068 1068 wctx.add([dst])
1069 1069 elif not dryrun:
1070 1070 wctx.copy(origsrc, dst)
1071 1071
1072 1072 def readrequires(opener, supported):
1073 1073 '''Reads and parses .hg/requires and checks if all entries found
1074 1074 are in the list of supported features.'''
1075 1075 requirements = set(opener.read("requires").splitlines())
1076 1076 missings = []
1077 1077 for r in requirements:
1078 1078 if r not in supported:
1079 1079 if not r or not r[0:1].isalnum():
1080 1080 raise error.RequirementError(_(".hg/requires file is corrupt"))
1081 1081 missings.append(r)
1082 1082 missings.sort()
1083 1083 if missings:
1084 1084 raise error.RequirementError(
1085 1085 _("repository requires features unknown to this Mercurial: %s")
1086 1086 % " ".join(missings),
1087 1087 hint=_("see https://mercurial-scm.org/wiki/MissingRequirement"
1088 1088 " for more information"))
1089 1089 return requirements
1090 1090
1091 1091 def writerequires(opener, requirements):
1092 1092 with opener('requires', 'w') as fp:
1093 1093 for r in sorted(requirements):
1094 1094 fp.write("%s\n" % r)
1095 1095
1096 1096 class filecachesubentry(object):
1097 1097 def __init__(self, path, stat):
1098 1098 self.path = path
1099 1099 self.cachestat = None
1100 1100 self._cacheable = None
1101 1101
1102 1102 if stat:
1103 1103 self.cachestat = filecachesubentry.stat(self.path)
1104 1104
1105 1105 if self.cachestat:
1106 1106 self._cacheable = self.cachestat.cacheable()
1107 1107 else:
1108 1108 # None means we don't know yet
1109 1109 self._cacheable = None
1110 1110
1111 1111 def refresh(self):
1112 1112 if self.cacheable():
1113 1113 self.cachestat = filecachesubentry.stat(self.path)
1114 1114
1115 1115 def cacheable(self):
1116 1116 if self._cacheable is not None:
1117 1117 return self._cacheable
1118 1118
1119 1119 # we don't know yet, assume it is for now
1120 1120 return True
1121 1121
1122 1122 def changed(self):
1123 1123 # no point in going further if we can't cache it
1124 1124 if not self.cacheable():
1125 1125 return True
1126 1126
1127 1127 newstat = filecachesubentry.stat(self.path)
1128 1128
1129 1129 # we may not know if it's cacheable yet, check again now
1130 1130 if newstat and self._cacheable is None:
1131 1131 self._cacheable = newstat.cacheable()
1132 1132
1133 1133 # check again
1134 1134 if not self._cacheable:
1135 1135 return True
1136 1136
1137 1137 if self.cachestat != newstat:
1138 1138 self.cachestat = newstat
1139 1139 return True
1140 1140 else:
1141 1141 return False
1142 1142
1143 1143 @staticmethod
1144 1144 def stat(path):
1145 1145 try:
1146 1146 return util.cachestat(path)
1147 1147 except OSError as e:
1148 1148 if e.errno != errno.ENOENT:
1149 1149 raise
1150 1150
1151 1151 class filecacheentry(object):
1152 1152 def __init__(self, paths, stat=True):
1153 1153 self._entries = []
1154 1154 for path in paths:
1155 1155 self._entries.append(filecachesubentry(path, stat))
1156 1156
1157 1157 def changed(self):
1158 1158 '''true if any entry has changed'''
1159 1159 for entry in self._entries:
1160 1160 if entry.changed():
1161 1161 return True
1162 1162 return False
1163 1163
1164 1164 def refresh(self):
1165 1165 for entry in self._entries:
1166 1166 entry.refresh()
1167 1167
1168 1168 class filecache(object):
1169 1169 """A property like decorator that tracks files under .hg/ for updates.
1170 1170
1171 1171 On first access, the files defined as arguments are stat()ed and the
1172 1172 results cached. The decorated function is called. The results are stashed
1173 1173 away in a ``_filecache`` dict on the object whose method is decorated.
1174 1174
1175 1175 On subsequent access, the cached result is returned.
1176 1176
1177 1177 On external property set operations, stat() calls are performed and the new
1178 1178 value is cached.
1179 1179
1180 1180 On property delete operations, cached data is removed.
1181 1181
1182 1182 When using the property API, cached data is always returned, if available:
1183 1183 no stat() is performed to check if the file has changed and if the function
1184 1184 needs to be called to reflect file changes.
1185 1185
1186 1186 Others can muck about with the state of the ``_filecache`` dict. e.g. they
1187 1187 can populate an entry before the property's getter is called. In this case,
1188 1188 entries in ``_filecache`` will be used during property operations,
1189 1189 if available. If the underlying file changes, it is up to external callers
1190 1190 to reflect this by e.g. calling ``delattr(obj, attr)`` to remove the cached
1191 1191 method result as well as possibly calling ``del obj._filecache[attr]`` to
1192 1192 remove the ``filecacheentry``.
1193 1193 """
1194 1194
1195 1195 def __init__(self, *paths):
1196 1196 self.paths = paths
1197 1197
1198 1198 def join(self, obj, fname):
1199 1199 """Used to compute the runtime path of a cached file.
1200 1200
1201 1201 Users should subclass filecache and provide their own version of this
1202 1202 function to call the appropriate join function on 'obj' (an instance
1203 1203 of the class that its member function was decorated).
1204 1204 """
1205 1205 raise NotImplementedError
1206 1206
1207 1207 def __call__(self, func):
1208 1208 self.func = func
1209 1209 self.sname = func.__name__
1210 1210 self.name = pycompat.sysbytes(self.sname)
1211 1211 return self
1212 1212
1213 1213 def __get__(self, obj, type=None):
1214 1214 # if accessed on the class, return the descriptor itself.
1215 1215 if obj is None:
1216 1216 return self
1217 1217 # do we need to check if the file changed?
1218 1218 if self.sname in obj.__dict__:
1219 1219 assert self.name in obj._filecache, self.name
1220 1220 return obj.__dict__[self.sname]
1221 1221
1222 1222 entry = obj._filecache.get(self.name)
1223 1223
1224 1224 if entry:
1225 1225 if entry.changed():
1226 1226 entry.obj = self.func(obj)
1227 1227 else:
1228 1228 paths = [self.join(obj, path) for path in self.paths]
1229 1229
1230 1230 # We stat -before- creating the object so our cache doesn't lie if
1231 1231 # a writer modified between the time we read and stat
1232 1232 entry = filecacheentry(paths, True)
1233 1233 entry.obj = self.func(obj)
1234 1234
1235 1235 obj._filecache[self.name] = entry
1236 1236
1237 1237 obj.__dict__[self.sname] = entry.obj
1238 1238 return entry.obj
1239 1239
1240 1240 def __set__(self, obj, value):
1241 1241 if self.name not in obj._filecache:
1242 1242 # we add an entry for the missing value because X in __dict__
1243 1243 # implies X in _filecache
1244 1244 paths = [self.join(obj, path) for path in self.paths]
1245 1245 ce = filecacheentry(paths, False)
1246 1246 obj._filecache[self.name] = ce
1247 1247 else:
1248 1248 ce = obj._filecache[self.name]
1249 1249
1250 1250 ce.obj = value # update cached copy
1251 1251 obj.__dict__[self.sname] = value # update copy returned by obj.x
1252 1252
1253 1253 def __delete__(self, obj):
1254 1254 try:
1255 1255 del obj.__dict__[self.sname]
1256 1256 except KeyError:
1257 1257 raise AttributeError(self.sname)
1258 1258
1259 1259 def extdatasource(repo, source):
1260 1260 """Gather a map of rev -> value dict from the specified source
1261 1261
1262 1262 A source spec is treated as a URL, with a special case shell: type
1263 1263 for parsing the output from a shell command.
1264 1264
1265 1265 The data is parsed as a series of newline-separated records where
1266 1266 each record is a revision specifier optionally followed by a space
1267 1267 and a freeform string value. If the revision is known locally, it
1268 1268 is converted to a rev, otherwise the record is skipped.
1269 1269
1270 1270 Note that both key and value are treated as UTF-8 and converted to
1271 1271 the local encoding. This allows uniformity between local and
1272 1272 remote data sources.
1273 1273 """
1274 1274
1275 1275 spec = repo.ui.config("extdata", source)
1276 1276 if not spec:
1277 1277 raise error.Abort(_("unknown extdata source '%s'") % source)
1278 1278
1279 1279 data = {}
1280 1280 src = proc = None
1281 1281 try:
1282 1282 if spec.startswith("shell:"):
1283 1283 # external commands should be run relative to the repo root
1284 1284 cmd = spec[6:]
1285 1285 proc = subprocess.Popen(cmd, shell=True, bufsize=-1,
1286 1286 close_fds=procutil.closefds,
1287 1287 stdout=subprocess.PIPE, cwd=repo.root)
1288 1288 src = proc.stdout
1289 1289 else:
1290 1290 # treat as a URL or file
1291 1291 src = url.open(repo.ui, spec)
1292 1292 for l in src:
1293 1293 if " " in l:
1294 1294 k, v = l.strip().split(" ", 1)
1295 1295 else:
1296 1296 k, v = l.strip(), ""
1297 1297
1298 1298 k = encoding.tolocal(k)
1299 1299 try:
1300 1300 data[revsingle(repo, k).rev()] = encoding.tolocal(v)
1301 1301 except (error.LookupError, error.RepoLookupError):
1302 1302 pass # we ignore data for nodes that don't exist locally
1303 1303 finally:
1304 1304 if proc:
1305 1305 proc.communicate()
1306 1306 if src:
1307 1307 src.close()
1308 1308 if proc and proc.returncode != 0:
1309 1309 raise error.Abort(_("extdata command '%s' failed: %s")
1310 1310 % (cmd, procutil.explainexit(proc.returncode)))
1311 1311
1312 1312 return data
1313 1313
1314 1314 def _locksub(repo, lock, envvar, cmd, environ=None, *args, **kwargs):
1315 1315 if lock is None:
1316 1316 raise error.LockInheritanceContractViolation(
1317 1317 'lock can only be inherited while held')
1318 1318 if environ is None:
1319 1319 environ = {}
1320 1320 with lock.inherit() as locker:
1321 1321 environ[envvar] = locker
1322 1322 return repo.ui.system(cmd, environ=environ, *args, **kwargs)
1323 1323
1324 1324 def wlocksub(repo, cmd, *args, **kwargs):
1325 1325 """run cmd as a subprocess that allows inheriting repo's wlock
1326 1326
1327 1327 This can only be called while the wlock is held. This takes all the
1328 1328 arguments that ui.system does, and returns the exit code of the
1329 1329 subprocess."""
1330 1330 return _locksub(repo, repo.currentwlock(), 'HG_WLOCK_LOCKER', cmd, *args,
1331 1331 **kwargs)
1332 1332
1333 1333 class progress(object):
1334 1334 def __init__(self, ui, topic, unit="", total=None):
1335 1335 self.ui = ui
1336 1336 self.pos = 0
1337 1337 self.topic = topic
1338 1338 self.unit = unit
1339 1339 self.total = total
1340 1340
1341 1341 def __enter__(self):
1342 1342 return self
1343 1343
1344 1344 def __exit__(self, exc_type, exc_value, exc_tb):
1345 1345 self.complete()
1346 1346
1347 1347 def update(self, pos, item="", total=None):
1348 1348 assert pos is not None
1349 1349 if total:
1350 1350 self.total = total
1351 1351 self.pos = pos
1352 1352 self._print(item)
1353 1353
1354 1354 def increment(self, step=1, item="", total=None):
1355 1355 self.update(self.pos + step, item, total)
1356 1356
1357 1357 def complete(self):
1358 1358 self.ui.progress(self.topic, None)
1359 1359
1360 1360 def _print(self, item):
1361 1361 self.ui.progress(self.topic, self.pos, item, self.unit,
1362 1362 self.total)
1363 1363
1364 1364 def gdinitconfig(ui):
1365 1365 """helper function to know if a repo should be created as general delta
1366 1366 """
1367 1367 # experimental config: format.generaldelta
1368 1368 return (ui.configbool('format', 'generaldelta')
1369 1369 or ui.configbool('format', 'usegeneraldelta')
1370 1370 or ui.configbool('format', 'sparse-revlog'))
1371 1371
1372 1372 def gddeltaconfig(ui):
1373 1373 """helper function to know if incoming delta should be optimised
1374 1374 """
1375 1375 # experimental config: format.generaldelta
1376 1376 return ui.configbool('format', 'generaldelta')
1377 1377
1378 1378 class simplekeyvaluefile(object):
1379 1379 """A simple file with key=value lines
1380 1380
1381 1381 Keys must be alphanumerics and start with a letter, values must not
1382 1382 contain '\n' characters"""
1383 1383 firstlinekey = '__firstline'
1384 1384
1385 1385 def __init__(self, vfs, path, keys=None):
1386 1386 self.vfs = vfs
1387 1387 self.path = path
1388 1388
1389 1389 def read(self, firstlinenonkeyval=False):
1390 1390 """Read the contents of a simple key-value file
1391 1391
1392 1392 'firstlinenonkeyval' indicates whether the first line of file should
1393 1393 be treated as a key-value pair or reuturned fully under the
1394 1394 __firstline key."""
1395 1395 lines = self.vfs.readlines(self.path)
1396 1396 d = {}
1397 1397 if firstlinenonkeyval:
1398 1398 if not lines:
1399 1399 e = _("empty simplekeyvalue file")
1400 1400 raise error.CorruptedState(e)
1401 1401 # we don't want to include '\n' in the __firstline
1402 1402 d[self.firstlinekey] = lines[0][:-1]
1403 1403 del lines[0]
1404 1404
1405 1405 try:
1406 1406 # the 'if line.strip()' part prevents us from failing on empty
1407 1407 # lines which only contain '\n' therefore are not skipped
1408 1408 # by 'if line'
1409 1409 updatedict = dict(line[:-1].split('=', 1) for line in lines
1410 1410 if line.strip())
1411 1411 if self.firstlinekey in updatedict:
1412 1412 e = _("%r can't be used as a key")
1413 1413 raise error.CorruptedState(e % self.firstlinekey)
1414 1414 d.update(updatedict)
1415 1415 except ValueError as e:
1416 1416 raise error.CorruptedState(str(e))
1417 1417 return d
1418 1418
1419 1419 def write(self, data, firstline=None):
1420 1420 """Write key=>value mapping to a file
1421 1421 data is a dict. Keys must be alphanumerical and start with a letter.
1422 1422 Values must not contain newline characters.
1423 1423
1424 1424 If 'firstline' is not None, it is written to file before
1425 1425 everything else, as it is, not in a key=value form"""
1426 1426 lines = []
1427 1427 if firstline is not None:
1428 1428 lines.append('%s\n' % firstline)
1429 1429
1430 1430 for k, v in data.items():
1431 1431 if k == self.firstlinekey:
1432 1432 e = "key name '%s' is reserved" % self.firstlinekey
1433 1433 raise error.ProgrammingError(e)
1434 1434 if not k[0:1].isalpha():
1435 1435 e = "keys must start with a letter in a key-value file"
1436 1436 raise error.ProgrammingError(e)
1437 1437 if not k.isalnum():
1438 1438 e = "invalid key name in a simple key-value file"
1439 1439 raise error.ProgrammingError(e)
1440 1440 if '\n' in v:
1441 1441 e = "invalid value in a simple key-value file"
1442 1442 raise error.ProgrammingError(e)
1443 1443 lines.append("%s=%s\n" % (k, v))
1444 1444 with self.vfs(self.path, mode='wb', atomictemp=True) as fp:
1445 1445 fp.write(''.join(lines))
1446 1446
1447 1447 _reportobsoletedsource = [
1448 1448 'debugobsolete',
1449 1449 'pull',
1450 1450 'push',
1451 1451 'serve',
1452 1452 'unbundle',
1453 1453 ]
1454 1454
1455 1455 _reportnewcssource = [
1456 1456 'pull',
1457 1457 'unbundle',
1458 1458 ]
1459 1459
1460 1460 def prefetchfiles(repo, revs, match):
1461 1461 """Invokes the registered file prefetch functions, allowing extensions to
1462 1462 ensure the corresponding files are available locally, before the command
1463 1463 uses them."""
1464 1464 if match:
1465 1465 # The command itself will complain about files that don't exist, so
1466 1466 # don't duplicate the message.
1467 1467 match = matchmod.badmatch(match, lambda fn, msg: None)
1468 1468 else:
1469 1469 match = matchall(repo)
1470 1470
1471 1471 fileprefetchhooks(repo, revs, match)
1472 1472
1473 1473 # a list of (repo, revs, match) prefetch functions
1474 1474 fileprefetchhooks = util.hooks()
1475 1475
1476 1476 # A marker that tells the evolve extension to suppress its own reporting
1477 1477 _reportstroubledchangesets = True
1478 1478
1479 1479 def registersummarycallback(repo, otr, txnname=''):
1480 1480 """register a callback to issue a summary after the transaction is closed
1481 1481 """
1482 1482 def txmatch(sources):
1483 1483 return any(txnname.startswith(source) for source in sources)
1484 1484
1485 1485 categories = []
1486 1486
1487 1487 def reportsummary(func):
1488 1488 """decorator for report callbacks."""
1489 1489 # The repoview life cycle is shorter than the one of the actual
1490 1490 # underlying repository. So the filtered object can die before the
1491 1491 # weakref is used leading to troubles. We keep a reference to the
1492 1492 # unfiltered object and restore the filtering when retrieving the
1493 1493 # repository through the weakref.
1494 1494 filtername = repo.filtername
1495 1495 reporef = weakref.ref(repo.unfiltered())
1496 1496 def wrapped(tr):
1497 1497 repo = reporef()
1498 1498 if filtername:
1499 1499 repo = repo.filtered(filtername)
1500 1500 func(repo, tr)
1501 1501 newcat = '%02i-txnreport' % len(categories)
1502 1502 otr.addpostclose(newcat, wrapped)
1503 1503 categories.append(newcat)
1504 1504 return wrapped
1505 1505
1506 1506 if txmatch(_reportobsoletedsource):
1507 1507 @reportsummary
1508 1508 def reportobsoleted(repo, tr):
1509 1509 obsoleted = obsutil.getobsoleted(repo, tr)
1510 1510 if obsoleted:
1511 1511 repo.ui.status(_('obsoleted %i changesets\n')
1512 1512 % len(obsoleted))
1513 1513
1514 1514 if (obsolete.isenabled(repo, obsolete.createmarkersopt) and
1515 1515 repo.ui.configbool('experimental', 'evolution.report-instabilities')):
1516 1516 instabilitytypes = [
1517 1517 ('orphan', 'orphan'),
1518 1518 ('phase-divergent', 'phasedivergent'),
1519 1519 ('content-divergent', 'contentdivergent'),
1520 1520 ]
1521 1521
1522 1522 def getinstabilitycounts(repo):
1523 1523 filtered = repo.changelog.filteredrevs
1524 1524 counts = {}
1525 1525 for instability, revset in instabilitytypes:
1526 1526 counts[instability] = len(set(obsolete.getrevs(repo, revset)) -
1527 1527 filtered)
1528 1528 return counts
1529 1529
1530 1530 oldinstabilitycounts = getinstabilitycounts(repo)
1531 1531 @reportsummary
1532 1532 def reportnewinstabilities(repo, tr):
1533 1533 newinstabilitycounts = getinstabilitycounts(repo)
1534 1534 for instability, revset in instabilitytypes:
1535 1535 delta = (newinstabilitycounts[instability] -
1536 1536 oldinstabilitycounts[instability])
1537 1537 msg = getinstabilitymessage(delta, instability)
1538 1538 if msg:
1539 1539 repo.ui.warn(msg)
1540 1540
1541 1541 if txmatch(_reportnewcssource):
1542 1542 @reportsummary
1543 1543 def reportnewcs(repo, tr):
1544 1544 """Report the range of new revisions pulled/unbundled."""
1545 1545 newrevs = tr.changes.get('revs', xrange(0, 0))
1546 1546 if not newrevs:
1547 1547 return
1548 1548
1549 1549 # Compute the bounds of new revisions' range, excluding obsoletes.
1550 1550 unfi = repo.unfiltered()
1551 1551 revs = unfi.revs('%ld and not obsolete()', newrevs)
1552 1552 if not revs:
1553 1553 # Got only obsoletes.
1554 1554 return
1555 1555 minrev, maxrev = repo[revs.min()], repo[revs.max()]
1556 1556
1557 1557 if minrev == maxrev:
1558 1558 revrange = minrev
1559 1559 else:
1560 1560 revrange = '%s:%s' % (minrev, maxrev)
1561 1561 repo.ui.status(_('new changesets %s\n') % revrange)
1562 1562
1563 1563 @reportsummary
1564 1564 def reportphasechanges(repo, tr):
1565 1565 """Report statistics of phase changes for changesets pre-existing
1566 1566 pull/unbundle.
1567 1567 """
1568 1568 newrevs = tr.changes.get('revs', xrange(0, 0))
1569 1569 phasetracking = tr.changes.get('phases', {})
1570 1570 if not phasetracking:
1571 1571 return
1572 1572 published = [
1573 1573 rev for rev, (old, new) in phasetracking.iteritems()
1574 1574 if new == phases.public and rev not in newrevs
1575 1575 ]
1576 1576 if not published:
1577 1577 return
1578 1578 repo.ui.status(_('%d local changesets published\n')
1579 1579 % len(published))
1580 1580
1581 1581 def getinstabilitymessage(delta, instability):
1582 1582 """function to return the message to show warning about new instabilities
1583 1583
1584 1584 exists as a separate function so that extension can wrap to show more
1585 1585 information like how to fix instabilities"""
1586 1586 if delta > 0:
1587 1587 return _('%i new %s changesets\n') % (delta, instability)
1588 1588
1589 1589 def nodesummaries(repo, nodes, maxnumnodes=4):
1590 1590 if len(nodes) <= maxnumnodes or repo.ui.verbose:
1591 1591 return ' '.join(short(h) for h in nodes)
1592 1592 first = ' '.join(short(h) for h in nodes[:maxnumnodes])
1593 1593 return _("%s and %d others") % (first, len(nodes) - maxnumnodes)
1594 1594
1595 1595 def enforcesinglehead(repo, tr, desc):
1596 1596 """check that no named branch has multiple heads"""
1597 1597 if desc in ('strip', 'repair'):
1598 1598 # skip the logic during strip
1599 1599 return
1600 1600 visible = repo.filtered('visible')
1601 1601 # possible improvement: we could restrict the check to affected branch
1602 1602 for name, heads in visible.branchmap().iteritems():
1603 1603 if len(heads) > 1:
1604 1604 msg = _('rejecting multiple heads on branch "%s"')
1605 1605 msg %= name
1606 1606 hint = _('%d heads: %s')
1607 1607 hint %= (len(heads), nodesummaries(repo, heads))
1608 1608 raise error.Abort(msg, hint=hint)
1609 1609
1610 1610 def wrapconvertsink(sink):
1611 1611 """Allow extensions to wrap the sink returned by convcmd.convertsink()
1612 1612 before it is used, whether or not the convert extension was formally loaded.
1613 1613 """
1614 1614 return sink
1615 1615
1616 1616 def unhidehashlikerevs(repo, specs, hiddentype):
1617 1617 """parse the user specs and unhide changesets whose hash or revision number
1618 1618 is passed.
1619 1619
1620 1620 hiddentype can be: 1) 'warn': warn while unhiding changesets
1621 1621 2) 'nowarn': don't warn while unhiding changesets
1622 1622
1623 1623 returns a repo object with the required changesets unhidden
1624 1624 """
1625 1625 if not repo.filtername or not repo.ui.configbool('experimental',
1626 1626 'directaccess'):
1627 1627 return repo
1628 1628
1629 1629 if repo.filtername not in ('visible', 'visible-hidden'):
1630 1630 return repo
1631 1631
1632 1632 symbols = set()
1633 1633 for spec in specs:
1634 1634 try:
1635 1635 tree = revsetlang.parse(spec)
1636 1636 except error.ParseError: # will be reported by scmutil.revrange()
1637 1637 continue
1638 1638
1639 1639 symbols.update(revsetlang.gethashlikesymbols(tree))
1640 1640
1641 1641 if not symbols:
1642 1642 return repo
1643 1643
1644 1644 revs = _getrevsfromsymbols(repo, symbols)
1645 1645
1646 1646 if not revs:
1647 1647 return repo
1648 1648
1649 1649 if hiddentype == 'warn':
1650 1650 unfi = repo.unfiltered()
1651 1651 revstr = ", ".join([pycompat.bytestr(unfi[l]) for l in revs])
1652 1652 repo.ui.warn(_("warning: accessing hidden changesets for write "
1653 1653 "operation: %s\n") % revstr)
1654 1654
1655 1655 # we have to use new filtername to separate branch/tags cache until we can
1656 1656 # disbale these cache when revisions are dynamically pinned.
1657 1657 return repo.filtered('visible-hidden', revs)
1658 1658
1659 1659 def _getrevsfromsymbols(repo, symbols):
1660 1660 """parse the list of symbols and returns a set of revision numbers of hidden
1661 1661 changesets present in symbols"""
1662 1662 revs = set()
1663 1663 unfi = repo.unfiltered()
1664 1664 unficl = unfi.changelog
1665 1665 cl = repo.changelog
1666 1666 tiprev = len(unficl)
1667 1667 allowrevnums = repo.ui.configbool('experimental', 'directaccess.revnums')
1668 1668 for s in symbols:
1669 1669 try:
1670 1670 n = int(s)
1671 1671 if n <= tiprev:
1672 1672 if not allowrevnums:
1673 1673 continue
1674 1674 else:
1675 1675 if n not in cl:
1676 1676 revs.add(n)
1677 1677 continue
1678 1678 except ValueError:
1679 1679 pass
1680 1680
1681 1681 try:
1682 1682 s = resolvehexnodeidprefix(unfi, s)
1683 1683 except (error.LookupError, error.WdirUnsupported):
1684 1684 s = None
1685 1685
1686 1686 if s is not None:
1687 1687 rev = unficl.rev(s)
1688 1688 if rev not in cl:
1689 1689 revs.add(rev)
1690 1690
1691 1691 return revs
1692 1692
1693 1693 def bookmarkrevs(repo, mark):
1694 1694 """
1695 1695 Select revisions reachable by a given bookmark
1696 1696 """
1697 1697 return repo.revs("ancestors(bookmark(%s)) - "
1698 1698 "ancestors(head() and not bookmark(%s)) - "
1699 1699 "ancestors(bookmark() and not bookmark(%s))",
1700 1700 mark, mark, mark)
@@ -1,1906 +1,1918 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 collections
11 11 import contextlib
12 12 import errno
13 13 import getpass
14 14 import inspect
15 15 import os
16 16 import re
17 17 import signal
18 18 import socket
19 19 import subprocess
20 20 import sys
21 21 import traceback
22 22
23 23 from .i18n import _
24 24 from .node import hex
25 25
26 26 from . import (
27 27 color,
28 28 config,
29 29 configitems,
30 30 encoding,
31 31 error,
32 32 formatter,
33 33 progress,
34 34 pycompat,
35 35 rcutil,
36 36 scmutil,
37 37 util,
38 38 )
39 39 from .utils import (
40 40 dateutil,
41 41 procutil,
42 42 stringutil,
43 43 )
44 44
45 45 urlreq = util.urlreq
46 46
47 47 # for use with str.translate(None, _keepalnum), to keep just alphanumerics
48 48 _keepalnum = ''.join(c for c in map(pycompat.bytechr, range(256))
49 49 if not c.isalnum())
50 50
51 51 # The config knobs that will be altered (if unset) by ui.tweakdefaults.
52 52 tweakrc = b"""
53 53 [ui]
54 54 # The rollback command is dangerous. As a rule, don't use it.
55 55 rollback = False
56 56 # Make `hg status` report copy information
57 57 statuscopies = yes
58 58 # Prefer curses UIs when available. Revert to plain-text with `text`.
59 59 interface = curses
60 60
61 61 [commands]
62 62 # Grep working directory by default.
63 63 grep.all-files = True
64 64 # Make `hg status` emit cwd-relative paths by default.
65 65 status.relative = yes
66 66 # Refuse to perform an `hg update` that would cause a file content merge
67 67 update.check = noconflict
68 68 # Show conflicts information in `hg status`
69 69 status.verbose = True
70 70
71 71 [diff]
72 72 git = 1
73 73 showfunc = 1
74 74 word-diff = 1
75 75 """
76 76
77 77 samplehgrcs = {
78 78 'user':
79 79 b"""# example user config (see 'hg help config' for more info)
80 80 [ui]
81 81 # name and email, e.g.
82 82 # username = Jane Doe <jdoe@example.com>
83 83 username =
84 84
85 85 # We recommend enabling tweakdefaults to get slight improvements to
86 86 # the UI over time. Make sure to set HGPLAIN in the environment when
87 87 # writing scripts!
88 88 # tweakdefaults = True
89 89
90 90 # uncomment to disable color in command output
91 91 # (see 'hg help color' for details)
92 92 # color = never
93 93
94 94 # uncomment to disable command output pagination
95 95 # (see 'hg help pager' for details)
96 96 # paginate = never
97 97
98 98 [extensions]
99 99 # uncomment these lines to enable some popular extensions
100 100 # (see 'hg help extensions' for more info)
101 101 #
102 102 # churn =
103 103 """,
104 104
105 105 'cloned':
106 106 b"""# example repository config (see 'hg help config' for more info)
107 107 [paths]
108 108 default = %s
109 109
110 110 # path aliases to other clones of this repo in URLs or filesystem paths
111 111 # (see 'hg help config.paths' for more info)
112 112 #
113 113 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
114 114 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
115 115 # my-clone = /home/jdoe/jdoes-clone
116 116
117 117 [ui]
118 118 # name and email (local to this repository, optional), e.g.
119 119 # username = Jane Doe <jdoe@example.com>
120 120 """,
121 121
122 122 'local':
123 123 b"""# example repository config (see 'hg help config' for more info)
124 124 [paths]
125 125 # path aliases to other clones of this repo in URLs or filesystem paths
126 126 # (see 'hg help config.paths' for more info)
127 127 #
128 128 # default = http://example.com/hg/example-repo
129 129 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
130 130 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
131 131 # my-clone = /home/jdoe/jdoes-clone
132 132
133 133 [ui]
134 134 # name and email (local to this repository, optional), e.g.
135 135 # username = Jane Doe <jdoe@example.com>
136 136 """,
137 137
138 138 'global':
139 139 b"""# example system-wide hg config (see 'hg help config' for more info)
140 140
141 141 [ui]
142 142 # uncomment to disable color in command output
143 143 # (see 'hg help color' for details)
144 144 # color = never
145 145
146 146 # uncomment to disable command output pagination
147 147 # (see 'hg help pager' for details)
148 148 # paginate = never
149 149
150 150 [extensions]
151 151 # uncomment these lines to enable some popular extensions
152 152 # (see 'hg help extensions' for more info)
153 153 #
154 154 # blackbox =
155 155 # churn =
156 156 """,
157 157 }
158 158
159 159 def _maybestrurl(maybebytes):
160 160 return pycompat.rapply(pycompat.strurl, maybebytes)
161 161
162 162 def _maybebytesurl(maybestr):
163 163 return pycompat.rapply(pycompat.bytesurl, maybestr)
164 164
165 165 class httppasswordmgrdbproxy(object):
166 166 """Delays loading urllib2 until it's needed."""
167 167 def __init__(self):
168 168 self._mgr = None
169 169
170 170 def _get_mgr(self):
171 171 if self._mgr is None:
172 172 self._mgr = urlreq.httppasswordmgrwithdefaultrealm()
173 173 return self._mgr
174 174
175 175 def add_password(self, realm, uris, user, passwd):
176 176 return self._get_mgr().add_password(
177 177 _maybestrurl(realm), _maybestrurl(uris),
178 178 _maybestrurl(user), _maybestrurl(passwd))
179 179
180 180 def find_user_password(self, realm, uri):
181 181 mgr = self._get_mgr()
182 182 return _maybebytesurl(mgr.find_user_password(_maybestrurl(realm),
183 183 _maybestrurl(uri)))
184 184
185 185 def _catchterm(*args):
186 186 raise error.SignalInterrupt
187 187
188 188 # unique object used to detect no default value has been provided when
189 189 # retrieving configuration value.
190 190 _unset = object()
191 191
192 192 # _reqexithandlers: callbacks run at the end of a request
193 193 _reqexithandlers = []
194 194
195 195 class ui(object):
196 196 def __init__(self, src=None):
197 197 """Create a fresh new ui object if no src given
198 198
199 199 Use uimod.ui.load() to create a ui which knows global and user configs.
200 200 In most cases, you should use ui.copy() to create a copy of an existing
201 201 ui object.
202 202 """
203 203 # _buffers: used for temporary capture of output
204 204 self._buffers = []
205 205 # 3-tuple describing how each buffer in the stack behaves.
206 206 # Values are (capture stderr, capture subprocesses, apply labels).
207 207 self._bufferstates = []
208 208 # When a buffer is active, defines whether we are expanding labels.
209 209 # This exists to prevent an extra list lookup.
210 210 self._bufferapplylabels = None
211 211 self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
212 212 self._reportuntrusted = True
213 213 self._knownconfig = configitems.coreitems
214 214 self._ocfg = config.config() # overlay
215 215 self._tcfg = config.config() # trusted
216 216 self._ucfg = config.config() # untrusted
217 217 self._trustusers = set()
218 218 self._trustgroups = set()
219 219 self.callhooks = True
220 220 # Insecure server connections requested.
221 221 self.insecureconnections = False
222 222 # Blocked time
223 223 self.logblockedtimes = False
224 224 # color mode: see mercurial/color.py for possible value
225 225 self._colormode = None
226 226 self._terminfoparams = {}
227 227 self._styles = {}
228 228 self._uninterruptible = False
229 229
230 230 if src:
231 231 self.fout = src.fout
232 232 self.ferr = src.ferr
233 233 self.fin = src.fin
234 234 self.pageractive = src.pageractive
235 235 self._disablepager = src._disablepager
236 236 self._tweaked = src._tweaked
237 237
238 238 self._tcfg = src._tcfg.copy()
239 239 self._ucfg = src._ucfg.copy()
240 240 self._ocfg = src._ocfg.copy()
241 241 self._trustusers = src._trustusers.copy()
242 242 self._trustgroups = src._trustgroups.copy()
243 243 self.environ = src.environ
244 244 self.callhooks = src.callhooks
245 245 self.insecureconnections = src.insecureconnections
246 246 self._colormode = src._colormode
247 247 self._terminfoparams = src._terminfoparams.copy()
248 248 self._styles = src._styles.copy()
249 249
250 250 self.fixconfig()
251 251
252 252 self.httppasswordmgrdb = src.httppasswordmgrdb
253 253 self._blockedtimes = src._blockedtimes
254 254 else:
255 255 self.fout = procutil.stdout
256 256 self.ferr = procutil.stderr
257 257 self.fin = procutil.stdin
258 258 self.pageractive = False
259 259 self._disablepager = False
260 260 self._tweaked = False
261 261
262 262 # shared read-only environment
263 263 self.environ = encoding.environ
264 264
265 265 self.httppasswordmgrdb = httppasswordmgrdbproxy()
266 266 self._blockedtimes = collections.defaultdict(int)
267 267
268 268 allowed = self.configlist('experimental', 'exportableenviron')
269 269 if '*' in allowed:
270 270 self._exportableenviron = self.environ
271 271 else:
272 272 self._exportableenviron = {}
273 273 for k in allowed:
274 274 if k in self.environ:
275 275 self._exportableenviron[k] = self.environ[k]
276 276
277 277 @classmethod
278 278 def load(cls):
279 279 """Create a ui and load global and user configs"""
280 280 u = cls()
281 281 # we always trust global config files and environment variables
282 282 for t, f in rcutil.rccomponents():
283 283 if t == 'path':
284 284 u.readconfig(f, trust=True)
285 285 elif t == 'items':
286 286 sections = set()
287 287 for section, name, value, source in f:
288 288 # do not set u._ocfg
289 289 # XXX clean this up once immutable config object is a thing
290 290 u._tcfg.set(section, name, value, source)
291 291 u._ucfg.set(section, name, value, source)
292 292 sections.add(section)
293 293 for section in sections:
294 294 u.fixconfig(section=section)
295 295 else:
296 296 raise error.ProgrammingError('unknown rctype: %s' % t)
297 297 u._maybetweakdefaults()
298 298 return u
299 299
300 300 def _maybetweakdefaults(self):
301 301 if not self.configbool('ui', 'tweakdefaults'):
302 302 return
303 303 if self._tweaked or self.plain('tweakdefaults'):
304 304 return
305 305
306 306 # Note: it is SUPER IMPORTANT that you set self._tweaked to
307 307 # True *before* any calls to setconfig(), otherwise you'll get
308 308 # infinite recursion between setconfig and this method.
309 309 #
310 310 # TODO: We should extract an inner method in setconfig() to
311 311 # avoid this weirdness.
312 312 self._tweaked = True
313 313 tmpcfg = config.config()
314 314 tmpcfg.parse('<tweakdefaults>', tweakrc)
315 315 for section in tmpcfg:
316 316 for name, value in tmpcfg.items(section):
317 317 if not self.hasconfig(section, name):
318 318 self.setconfig(section, name, value, "<tweakdefaults>")
319 319
320 320 def copy(self):
321 321 return self.__class__(self)
322 322
323 323 def resetstate(self):
324 324 """Clear internal state that shouldn't persist across commands"""
325 325 if self._progbar:
326 326 self._progbar.resetstate() # reset last-print time of progress bar
327 327 self.httppasswordmgrdb = httppasswordmgrdbproxy()
328 328
329 329 @contextlib.contextmanager
330 330 def timeblockedsection(self, key):
331 331 # this is open-coded below - search for timeblockedsection to find them
332 332 starttime = util.timer()
333 333 try:
334 334 yield
335 335 finally:
336 336 self._blockedtimes[key + '_blocked'] += \
337 337 (util.timer() - starttime) * 1000
338 338
339 339 @contextlib.contextmanager
340 340 def uninterruptable(self):
341 341 """Mark an operation as unsafe.
342 342
343 343 Most operations on a repository are safe to interrupt, but a
344 344 few are risky (for example repair.strip). This context manager
345 345 lets you advise Mercurial that something risky is happening so
346 346 that control-C etc can be blocked if desired.
347 347 """
348 348 enabled = self.configbool('experimental', 'nointerrupt')
349 349 if (enabled and
350 350 self.configbool('experimental', 'nointerrupt-interactiveonly')):
351 351 enabled = self.interactive()
352 352 if self._uninterruptible or not enabled:
353 353 # if nointerrupt support is turned off, the process isn't
354 354 # interactive, or we're already in an uninterruptable
355 355 # block, do nothing.
356 356 yield
357 357 return
358 358 def warn():
359 359 self.warn(_("shutting down cleanly\n"))
360 360 self.warn(
361 361 _("press ^C again to terminate immediately (dangerous)\n"))
362 362 return True
363 363 with procutil.uninterruptable(warn):
364 364 try:
365 365 self._uninterruptible = True
366 366 yield
367 367 finally:
368 368 self._uninterruptible = False
369 369
370 370 def formatter(self, topic, opts):
371 371 return formatter.formatter(self, self, topic, opts)
372 372
373 373 def _trusted(self, fp, f):
374 374 st = util.fstat(fp)
375 375 if util.isowner(st):
376 376 return True
377 377
378 378 tusers, tgroups = self._trustusers, self._trustgroups
379 379 if '*' in tusers or '*' in tgroups:
380 380 return True
381 381
382 382 user = util.username(st.st_uid)
383 383 group = util.groupname(st.st_gid)
384 384 if user in tusers or group in tgroups or user == util.username():
385 385 return True
386 386
387 387 if self._reportuntrusted:
388 388 self.warn(_('not trusting file %s from untrusted '
389 389 'user %s, group %s\n') % (f, user, group))
390 390 return False
391 391
392 392 def readconfig(self, filename, root=None, trust=False,
393 393 sections=None, remap=None):
394 394 try:
395 395 fp = open(filename, r'rb')
396 396 except IOError:
397 397 if not sections: # ignore unless we were looking for something
398 398 return
399 399 raise
400 400
401 401 cfg = config.config()
402 402 trusted = sections or trust or self._trusted(fp, filename)
403 403
404 404 try:
405 405 cfg.read(filename, fp, sections=sections, remap=remap)
406 406 fp.close()
407 407 except error.ConfigError as inst:
408 408 if trusted:
409 409 raise
410 410 self.warn(_("ignored: %s\n") % stringutil.forcebytestr(inst))
411 411
412 412 if self.plain():
413 413 for k in ('debug', 'fallbackencoding', 'quiet', 'slash',
414 414 'logtemplate', 'statuscopies', 'style',
415 415 'traceback', 'verbose'):
416 416 if k in cfg['ui']:
417 417 del cfg['ui'][k]
418 418 for k, v in cfg.items('defaults'):
419 419 del cfg['defaults'][k]
420 420 for k, v in cfg.items('commands'):
421 421 del cfg['commands'][k]
422 422 # Don't remove aliases from the configuration if in the exceptionlist
423 423 if self.plain('alias'):
424 424 for k, v in cfg.items('alias'):
425 425 del cfg['alias'][k]
426 426 if self.plain('revsetalias'):
427 427 for k, v in cfg.items('revsetalias'):
428 428 del cfg['revsetalias'][k]
429 429 if self.plain('templatealias'):
430 430 for k, v in cfg.items('templatealias'):
431 431 del cfg['templatealias'][k]
432 432
433 433 if trusted:
434 434 self._tcfg.update(cfg)
435 435 self._tcfg.update(self._ocfg)
436 436 self._ucfg.update(cfg)
437 437 self._ucfg.update(self._ocfg)
438 438
439 439 if root is None:
440 440 root = os.path.expanduser('~')
441 441 self.fixconfig(root=root)
442 442
443 443 def fixconfig(self, root=None, section=None):
444 444 if section in (None, 'paths'):
445 445 # expand vars and ~
446 446 # translate paths relative to root (or home) into absolute paths
447 447 root = root or pycompat.getcwd()
448 448 for c in self._tcfg, self._ucfg, self._ocfg:
449 449 for n, p in c.items('paths'):
450 450 # Ignore sub-options.
451 451 if ':' in n:
452 452 continue
453 453 if not p:
454 454 continue
455 455 if '%%' in p:
456 456 s = self.configsource('paths', n) or 'none'
457 457 self.warn(_("(deprecated '%%' in path %s=%s from %s)\n")
458 458 % (n, p, s))
459 459 p = p.replace('%%', '%')
460 460 p = util.expandpath(p)
461 461 if not util.hasscheme(p) and not os.path.isabs(p):
462 462 p = os.path.normpath(os.path.join(root, p))
463 463 c.set("paths", n, p)
464 464
465 465 if section in (None, 'ui'):
466 466 # update ui options
467 467 self.debugflag = self.configbool('ui', 'debug')
468 468 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
469 469 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
470 470 if self.verbose and self.quiet:
471 471 self.quiet = self.verbose = False
472 472 self._reportuntrusted = self.debugflag or self.configbool("ui",
473 473 "report_untrusted")
474 474 self.tracebackflag = self.configbool('ui', 'traceback')
475 475 self.logblockedtimes = self.configbool('ui', 'logblockedtimes')
476 476
477 477 if section in (None, 'trusted'):
478 478 # update trust information
479 479 self._trustusers.update(self.configlist('trusted', 'users'))
480 480 self._trustgroups.update(self.configlist('trusted', 'groups'))
481 481
482 482 def backupconfig(self, section, item):
483 483 return (self._ocfg.backup(section, item),
484 484 self._tcfg.backup(section, item),
485 485 self._ucfg.backup(section, item),)
486 486 def restoreconfig(self, data):
487 487 self._ocfg.restore(data[0])
488 488 self._tcfg.restore(data[1])
489 489 self._ucfg.restore(data[2])
490 490
491 491 def setconfig(self, section, name, value, source=''):
492 492 for cfg in (self._ocfg, self._tcfg, self._ucfg):
493 493 cfg.set(section, name, value, source)
494 494 self.fixconfig(section=section)
495 495 self._maybetweakdefaults()
496 496
497 497 def _data(self, untrusted):
498 498 return untrusted and self._ucfg or self._tcfg
499 499
500 500 def configsource(self, section, name, untrusted=False):
501 501 return self._data(untrusted).source(section, name)
502 502
503 503 def config(self, section, name, default=_unset, untrusted=False):
504 504 """return the plain string version of a config"""
505 505 value = self._config(section, name, default=default,
506 506 untrusted=untrusted)
507 507 if value is _unset:
508 508 return None
509 509 return value
510 510
511 511 def _config(self, section, name, default=_unset, untrusted=False):
512 512 value = itemdefault = default
513 513 item = self._knownconfig.get(section, {}).get(name)
514 514 alternates = [(section, name)]
515 515
516 516 if item is not None:
517 517 alternates.extend(item.alias)
518 518 if callable(item.default):
519 519 itemdefault = item.default()
520 520 else:
521 521 itemdefault = item.default
522 522 else:
523 523 msg = ("accessing unregistered config item: '%s.%s'")
524 524 msg %= (section, name)
525 525 self.develwarn(msg, 2, 'warn-config-unknown')
526 526
527 527 if default is _unset:
528 528 if item is None:
529 529 value = default
530 530 elif item.default is configitems.dynamicdefault:
531 531 value = None
532 532 msg = "config item requires an explicit default value: '%s.%s'"
533 533 msg %= (section, name)
534 534 self.develwarn(msg, 2, 'warn-config-default')
535 535 else:
536 536 value = itemdefault
537 537 elif (item is not None
538 538 and item.default is not configitems.dynamicdefault
539 539 and default != itemdefault):
540 540 msg = ("specifying a mismatched default value for a registered "
541 541 "config item: '%s.%s' '%s'")
542 542 msg %= (section, name, pycompat.bytestr(default))
543 543 self.develwarn(msg, 2, 'warn-config-default')
544 544
545 545 for s, n in alternates:
546 546 candidate = self._data(untrusted).get(s, n, None)
547 547 if candidate is not None:
548 548 value = candidate
549 549 section = s
550 550 name = n
551 551 break
552 552
553 553 if self.debugflag and not untrusted and self._reportuntrusted:
554 554 for s, n in alternates:
555 555 uvalue = self._ucfg.get(s, n)
556 556 if uvalue is not None and uvalue != value:
557 557 self.debug("ignoring untrusted configuration option "
558 558 "%s.%s = %s\n" % (s, n, uvalue))
559 559 return value
560 560
561 561 def configsuboptions(self, section, name, default=_unset, untrusted=False):
562 562 """Get a config option and all sub-options.
563 563
564 564 Some config options have sub-options that are declared with the
565 565 format "key:opt = value". This method is used to return the main
566 566 option and all its declared sub-options.
567 567
568 568 Returns a 2-tuple of ``(option, sub-options)``, where `sub-options``
569 569 is a dict of defined sub-options where keys and values are strings.
570 570 """
571 571 main = self.config(section, name, default, untrusted=untrusted)
572 572 data = self._data(untrusted)
573 573 sub = {}
574 574 prefix = '%s:' % name
575 575 for k, v in data.items(section):
576 576 if k.startswith(prefix):
577 577 sub[k[len(prefix):]] = v
578 578
579 579 if self.debugflag and not untrusted and self._reportuntrusted:
580 580 for k, v in sub.items():
581 581 uvalue = self._ucfg.get(section, '%s:%s' % (name, k))
582 582 if uvalue is not None and uvalue != v:
583 583 self.debug('ignoring untrusted configuration option '
584 584 '%s:%s.%s = %s\n' % (section, name, k, uvalue))
585 585
586 586 return main, sub
587 587
588 588 def configpath(self, section, name, default=_unset, untrusted=False):
589 589 'get a path config item, expanded relative to repo root or config file'
590 590 v = self.config(section, name, default, untrusted)
591 591 if v is None:
592 592 return None
593 593 if not os.path.isabs(v) or "://" not in v:
594 594 src = self.configsource(section, name, untrusted)
595 595 if ':' in src:
596 596 base = os.path.dirname(src.rsplit(':')[0])
597 597 v = os.path.join(base, os.path.expanduser(v))
598 598 return v
599 599
600 600 def configbool(self, section, name, default=_unset, untrusted=False):
601 601 """parse a configuration element as a boolean
602 602
603 603 >>> u = ui(); s = b'foo'
604 604 >>> u.setconfig(s, b'true', b'yes')
605 605 >>> u.configbool(s, b'true')
606 606 True
607 607 >>> u.setconfig(s, b'false', b'no')
608 608 >>> u.configbool(s, b'false')
609 609 False
610 610 >>> u.configbool(s, b'unknown')
611 611 False
612 612 >>> u.configbool(s, b'unknown', True)
613 613 True
614 614 >>> u.setconfig(s, b'invalid', b'somevalue')
615 615 >>> u.configbool(s, b'invalid')
616 616 Traceback (most recent call last):
617 617 ...
618 618 ConfigError: foo.invalid is not a boolean ('somevalue')
619 619 """
620 620
621 621 v = self._config(section, name, default, untrusted=untrusted)
622 622 if v is None:
623 623 return v
624 624 if v is _unset:
625 625 if default is _unset:
626 626 return False
627 627 return default
628 628 if isinstance(v, bool):
629 629 return v
630 630 b = stringutil.parsebool(v)
631 631 if b is None:
632 632 raise error.ConfigError(_("%s.%s is not a boolean ('%s')")
633 633 % (section, name, v))
634 634 return b
635 635
636 636 def configwith(self, convert, section, name, default=_unset,
637 637 desc=None, untrusted=False):
638 638 """parse a configuration element with a conversion function
639 639
640 640 >>> u = ui(); s = b'foo'
641 641 >>> u.setconfig(s, b'float1', b'42')
642 642 >>> u.configwith(float, s, b'float1')
643 643 42.0
644 644 >>> u.setconfig(s, b'float2', b'-4.25')
645 645 >>> u.configwith(float, s, b'float2')
646 646 -4.25
647 647 >>> u.configwith(float, s, b'unknown', 7)
648 648 7.0
649 649 >>> u.setconfig(s, b'invalid', b'somevalue')
650 650 >>> u.configwith(float, s, b'invalid')
651 651 Traceback (most recent call last):
652 652 ...
653 653 ConfigError: foo.invalid is not a valid float ('somevalue')
654 654 >>> u.configwith(float, s, b'invalid', desc=b'womble')
655 655 Traceback (most recent call last):
656 656 ...
657 657 ConfigError: foo.invalid is not a valid womble ('somevalue')
658 658 """
659 659
660 660 v = self.config(section, name, default, untrusted)
661 661 if v is None:
662 662 return v # do not attempt to convert None
663 663 try:
664 664 return convert(v)
665 665 except (ValueError, error.ParseError):
666 666 if desc is None:
667 667 desc = pycompat.sysbytes(convert.__name__)
668 668 raise error.ConfigError(_("%s.%s is not a valid %s ('%s')")
669 669 % (section, name, desc, v))
670 670
671 671 def configint(self, section, name, default=_unset, untrusted=False):
672 672 """parse a configuration element as an integer
673 673
674 674 >>> u = ui(); s = b'foo'
675 675 >>> u.setconfig(s, b'int1', b'42')
676 676 >>> u.configint(s, b'int1')
677 677 42
678 678 >>> u.setconfig(s, b'int2', b'-42')
679 679 >>> u.configint(s, b'int2')
680 680 -42
681 681 >>> u.configint(s, b'unknown', 7)
682 682 7
683 683 >>> u.setconfig(s, b'invalid', b'somevalue')
684 684 >>> u.configint(s, b'invalid')
685 685 Traceback (most recent call last):
686 686 ...
687 687 ConfigError: foo.invalid is not a valid integer ('somevalue')
688 688 """
689 689
690 690 return self.configwith(int, section, name, default, 'integer',
691 691 untrusted)
692 692
693 693 def configbytes(self, section, name, default=_unset, untrusted=False):
694 694 """parse a configuration element as a quantity in bytes
695 695
696 696 Units can be specified as b (bytes), k or kb (kilobytes), m or
697 697 mb (megabytes), g or gb (gigabytes).
698 698
699 699 >>> u = ui(); s = b'foo'
700 700 >>> u.setconfig(s, b'val1', b'42')
701 701 >>> u.configbytes(s, b'val1')
702 702 42
703 703 >>> u.setconfig(s, b'val2', b'42.5 kb')
704 704 >>> u.configbytes(s, b'val2')
705 705 43520
706 706 >>> u.configbytes(s, b'unknown', b'7 MB')
707 707 7340032
708 708 >>> u.setconfig(s, b'invalid', b'somevalue')
709 709 >>> u.configbytes(s, b'invalid')
710 710 Traceback (most recent call last):
711 711 ...
712 712 ConfigError: foo.invalid is not a byte quantity ('somevalue')
713 713 """
714 714
715 715 value = self._config(section, name, default, untrusted)
716 716 if value is _unset:
717 717 if default is _unset:
718 718 default = 0
719 719 value = default
720 720 if not isinstance(value, bytes):
721 721 return value
722 722 try:
723 723 return util.sizetoint(value)
724 724 except error.ParseError:
725 725 raise error.ConfigError(_("%s.%s is not a byte quantity ('%s')")
726 726 % (section, name, value))
727 727
728 728 def configlist(self, section, name, default=_unset, untrusted=False):
729 729 """parse a configuration element as a list of comma/space separated
730 730 strings
731 731
732 732 >>> u = ui(); s = b'foo'
733 733 >>> u.setconfig(s, b'list1', b'this,is "a small" ,test')
734 734 >>> u.configlist(s, b'list1')
735 735 ['this', 'is', 'a small', 'test']
736 736 >>> u.setconfig(s, b'list2', b'this, is "a small" , test ')
737 737 >>> u.configlist(s, b'list2')
738 738 ['this', 'is', 'a small', 'test']
739 739 """
740 740 # default is not always a list
741 741 v = self.configwith(config.parselist, section, name, default,
742 742 'list', untrusted)
743 743 if isinstance(v, bytes):
744 744 return config.parselist(v)
745 745 elif v is None:
746 746 return []
747 747 return v
748 748
749 749 def configdate(self, section, name, default=_unset, untrusted=False):
750 750 """parse a configuration element as a tuple of ints
751 751
752 752 >>> u = ui(); s = b'foo'
753 753 >>> u.setconfig(s, b'date', b'0 0')
754 754 >>> u.configdate(s, b'date')
755 755 (0, 0)
756 756 """
757 757 if self.config(section, name, default, untrusted):
758 758 return self.configwith(dateutil.parsedate, section, name, default,
759 759 'date', untrusted)
760 760 if default is _unset:
761 761 return None
762 762 return default
763 763
764 764 def hasconfig(self, section, name, untrusted=False):
765 765 return self._data(untrusted).hasitem(section, name)
766 766
767 767 def has_section(self, section, untrusted=False):
768 768 '''tell whether section exists in config.'''
769 769 return section in self._data(untrusted)
770 770
771 771 def configitems(self, section, untrusted=False, ignoresub=False):
772 772 items = self._data(untrusted).items(section)
773 773 if ignoresub:
774 774 items = [i for i in items if ':' not in i[0]]
775 775 if self.debugflag and not untrusted and self._reportuntrusted:
776 776 for k, v in self._ucfg.items(section):
777 777 if self._tcfg.get(section, k) != v:
778 778 self.debug("ignoring untrusted configuration option "
779 779 "%s.%s = %s\n" % (section, k, v))
780 780 return items
781 781
782 782 def walkconfig(self, untrusted=False):
783 783 cfg = self._data(untrusted)
784 784 for section in cfg.sections():
785 785 for name, value in self.configitems(section, untrusted):
786 786 yield section, name, value
787 787
788 788 def plain(self, feature=None):
789 789 '''is plain mode active?
790 790
791 791 Plain mode means that all configuration variables which affect
792 792 the behavior and output of Mercurial should be
793 793 ignored. Additionally, the output should be stable,
794 794 reproducible and suitable for use in scripts or applications.
795 795
796 796 The only way to trigger plain mode is by setting either the
797 797 `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
798 798
799 799 The return value can either be
800 800 - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
801 801 - False if feature is disabled by default and not included in HGPLAIN
802 802 - True otherwise
803 803 '''
804 804 if ('HGPLAIN' not in encoding.environ and
805 805 'HGPLAINEXCEPT' not in encoding.environ):
806 806 return False
807 807 exceptions = encoding.environ.get('HGPLAINEXCEPT',
808 808 '').strip().split(',')
809 809 # TODO: add support for HGPLAIN=+feature,-feature syntax
810 810 if '+strictflags' not in encoding.environ.get('HGPLAIN', '').split(','):
811 811 exceptions.append('strictflags')
812 812 if feature and exceptions:
813 813 return feature not in exceptions
814 814 return True
815 815
816 816 def username(self, acceptempty=False):
817 817 """Return default username to be used in commits.
818 818
819 819 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
820 820 and stop searching if one of these is set.
821 821 If not found and acceptempty is True, returns None.
822 822 If not found and ui.askusername is True, ask the user, else use
823 823 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
824 824 If no username could be found, raise an Abort error.
825 825 """
826 826 user = encoding.environ.get("HGUSER")
827 827 if user is None:
828 828 user = self.config("ui", "username")
829 829 if user is not None:
830 830 user = os.path.expandvars(user)
831 831 if user is None:
832 832 user = encoding.environ.get("EMAIL")
833 833 if user is None and acceptempty:
834 834 return user
835 835 if user is None and self.configbool("ui", "askusername"):
836 836 user = self.prompt(_("enter a commit username:"), default=None)
837 837 if user is None and not self.interactive():
838 838 try:
839 839 user = '%s@%s' % (procutil.getuser(),
840 840 encoding.strtolocal(socket.getfqdn()))
841 841 self.warn(_("no username found, using '%s' instead\n") % user)
842 842 except KeyError:
843 843 pass
844 844 if not user:
845 845 raise error.Abort(_('no username supplied'),
846 846 hint=_("use 'hg config --edit' "
847 847 'to set your username'))
848 848 if "\n" in user:
849 849 raise error.Abort(_("username %r contains a newline\n")
850 850 % pycompat.bytestr(user))
851 851 return user
852 852
853 853 def shortuser(self, user):
854 854 """Return a short representation of a user name or email address."""
855 855 if not self.verbose:
856 856 user = stringutil.shortuser(user)
857 857 return user
858 858
859 859 def expandpath(self, loc, default=None):
860 860 """Return repository location relative to cwd or from [paths]"""
861 861 try:
862 862 p = self.paths.getpath(loc)
863 863 if p:
864 864 return p.rawloc
865 865 except error.RepoError:
866 866 pass
867 867
868 868 if default:
869 869 try:
870 870 p = self.paths.getpath(default)
871 871 if p:
872 872 return p.rawloc
873 873 except error.RepoError:
874 874 pass
875 875
876 876 return loc
877 877
878 878 @util.propertycache
879 879 def paths(self):
880 880 return paths(self)
881 881
882 882 def pushbuffer(self, error=False, subproc=False, labeled=False):
883 883 """install a buffer to capture standard output of the ui object
884 884
885 885 If error is True, the error output will be captured too.
886 886
887 887 If subproc is True, output from subprocesses (typically hooks) will be
888 888 captured too.
889 889
890 890 If labeled is True, any labels associated with buffered
891 891 output will be handled. By default, this has no effect
892 892 on the output returned, but extensions and GUI tools may
893 893 handle this argument and returned styled output. If output
894 894 is being buffered so it can be captured and parsed or
895 895 processed, labeled should not be set to True.
896 896 """
897 897 self._buffers.append([])
898 898 self._bufferstates.append((error, subproc, labeled))
899 899 self._bufferapplylabels = labeled
900 900
901 901 def popbuffer(self):
902 902 '''pop the last buffer and return the buffered output'''
903 903 self._bufferstates.pop()
904 904 if self._bufferstates:
905 905 self._bufferapplylabels = self._bufferstates[-1][2]
906 906 else:
907 907 self._bufferapplylabels = None
908 908
909 909 return "".join(self._buffers.pop())
910 910
911 911 def canwritewithoutlabels(self):
912 912 '''check if write skips the label'''
913 913 if self._buffers and not self._bufferapplylabels:
914 914 return True
915 915 return self._colormode is None
916 916
917 917 def canbatchlabeledwrites(self):
918 918 '''check if write calls with labels are batchable'''
919 919 # Windows color printing is special, see ``write``.
920 920 return self._colormode != 'win32'
921 921
922 922 def write(self, *args, **opts):
923 923 '''write args to output
924 924
925 925 By default, this method simply writes to the buffer or stdout.
926 926 Color mode can be set on the UI class to have the output decorated
927 927 with color modifier before being written to stdout.
928 928
929 929 The color used is controlled by an optional keyword argument, "label".
930 930 This should be a string containing label names separated by space.
931 931 Label names take the form of "topic.type". For example, ui.debug()
932 932 issues a label of "ui.debug".
933 933
934 934 When labeling output for a specific command, a label of
935 935 "cmdname.type" is recommended. For example, status issues
936 936 a label of "status.modified" for modified files.
937 937 '''
938 938 if self._buffers:
939 939 if self._bufferapplylabels:
940 940 label = opts.get(r'label', '')
941 941 self._buffers[-1].extend(self.label(a, label) for a in args)
942 942 else:
943 943 self._buffers[-1].extend(args)
944 944 else:
945 945 self._writenobuf(*args, **opts)
946 946
947 947 def _writenobuf(self, *args, **opts):
948 948 if self._colormode == 'win32':
949 949 # windows color printing is its own can of crab, defer to
950 950 # the color module and that is it.
951 951 color.win32print(self, self._write, *args, **opts)
952 952 else:
953 953 msgs = args
954 954 if self._colormode is not None:
955 955 label = opts.get(r'label', '')
956 956 msgs = [self.label(a, label) for a in args]
957 957 self._write(*msgs, **opts)
958 958
959 959 def _write(self, *msgs, **opts):
960 960 self._progclear()
961 961 # opencode timeblockedsection because this is a critical path
962 962 starttime = util.timer()
963 963 try:
964 964 self.fout.write(''.join(msgs))
965 965 except IOError as err:
966 966 raise error.StdioError(err)
967 967 finally:
968 968 self._blockedtimes['stdio_blocked'] += \
969 969 (util.timer() - starttime) * 1000
970 970
971 971 def write_err(self, *args, **opts):
972 972 self._progclear()
973 973 if self._bufferstates and self._bufferstates[-1][0]:
974 974 self.write(*args, **opts)
975 975 elif self._colormode == 'win32':
976 976 # windows color printing is its own can of crab, defer to
977 977 # the color module and that is it.
978 978 color.win32print(self, self._write_err, *args, **opts)
979 979 else:
980 980 msgs = args
981 981 if self._colormode is not None:
982 982 label = opts.get(r'label', '')
983 983 msgs = [self.label(a, label) for a in args]
984 984 self._write_err(*msgs, **opts)
985 985
986 986 def _write_err(self, *msgs, **opts):
987 987 try:
988 988 with self.timeblockedsection('stdio'):
989 989 if not getattr(self.fout, 'closed', False):
990 990 self.fout.flush()
991 991 for a in msgs:
992 992 self.ferr.write(a)
993 993 # stderr may be buffered under win32 when redirected to files,
994 994 # including stdout.
995 995 if not getattr(self.ferr, 'closed', False):
996 996 self.ferr.flush()
997 997 except IOError as inst:
998 998 if inst.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
999 999 raise error.StdioError(inst)
1000 1000
1001 1001 def flush(self):
1002 1002 # opencode timeblockedsection because this is a critical path
1003 1003 starttime = util.timer()
1004 1004 try:
1005 1005 try:
1006 1006 self.fout.flush()
1007 1007 except IOError as err:
1008 1008 if err.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
1009 1009 raise error.StdioError(err)
1010 1010 finally:
1011 1011 try:
1012 1012 self.ferr.flush()
1013 1013 except IOError as err:
1014 1014 if err.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
1015 1015 raise error.StdioError(err)
1016 1016 finally:
1017 1017 self._blockedtimes['stdio_blocked'] += \
1018 1018 (util.timer() - starttime) * 1000
1019 1019
1020 1020 def _isatty(self, fh):
1021 1021 if self.configbool('ui', 'nontty'):
1022 1022 return False
1023 1023 return procutil.isatty(fh)
1024 1024
1025 1025 def disablepager(self):
1026 1026 self._disablepager = True
1027 1027
1028 1028 def pager(self, command):
1029 1029 """Start a pager for subsequent command output.
1030 1030
1031 1031 Commands which produce a long stream of output should call
1032 1032 this function to activate the user's preferred pagination
1033 1033 mechanism (which may be no pager). Calling this function
1034 1034 precludes any future use of interactive functionality, such as
1035 1035 prompting the user or activating curses.
1036 1036
1037 1037 Args:
1038 1038 command: The full, non-aliased name of the command. That is, "log"
1039 1039 not "history, "summary" not "summ", etc.
1040 1040 """
1041 1041 if (self._disablepager
1042 1042 or self.pageractive):
1043 1043 # how pager should do is already determined
1044 1044 return
1045 1045
1046 1046 if not command.startswith('internal-always-') and (
1047 1047 # explicit --pager=on (= 'internal-always-' prefix) should
1048 1048 # take precedence over disabling factors below
1049 1049 command in self.configlist('pager', 'ignore')
1050 1050 or not self.configbool('ui', 'paginate')
1051 1051 or not self.configbool('pager', 'attend-' + command, True)
1052 1052 # TODO: if we want to allow HGPLAINEXCEPT=pager,
1053 1053 # formatted() will need some adjustment.
1054 1054 or not self.formatted()
1055 1055 or self.plain()
1056 1056 or self._buffers
1057 1057 # TODO: expose debugger-enabled on the UI object
1058 1058 or '--debugger' in pycompat.sysargv):
1059 1059 # We only want to paginate if the ui appears to be
1060 1060 # interactive, the user didn't say HGPLAIN or
1061 1061 # HGPLAINEXCEPT=pager, and the user didn't specify --debug.
1062 1062 return
1063 1063
1064 1064 pagercmd = self.config('pager', 'pager', rcutil.fallbackpager)
1065 1065 if not pagercmd:
1066 1066 return
1067 1067
1068 1068 pagerenv = {}
1069 1069 for name, value in rcutil.defaultpagerenv().items():
1070 1070 if name not in encoding.environ:
1071 1071 pagerenv[name] = value
1072 1072
1073 1073 self.debug('starting pager for command %r\n' % command)
1074 1074 self.flush()
1075 1075
1076 1076 wasformatted = self.formatted()
1077 1077 if util.safehasattr(signal, "SIGPIPE"):
1078 1078 signal.signal(signal.SIGPIPE, _catchterm)
1079 1079 if self._runpager(pagercmd, pagerenv):
1080 1080 self.pageractive = True
1081 1081 # Preserve the formatted-ness of the UI. This is important
1082 1082 # because we mess with stdout, which might confuse
1083 1083 # auto-detection of things being formatted.
1084 1084 self.setconfig('ui', 'formatted', wasformatted, 'pager')
1085 1085 self.setconfig('ui', 'interactive', False, 'pager')
1086 1086
1087 1087 # If pagermode differs from color.mode, reconfigure color now that
1088 1088 # pageractive is set.
1089 1089 cm = self._colormode
1090 1090 if cm != self.config('color', 'pagermode', cm):
1091 1091 color.setup(self)
1092 1092 else:
1093 1093 # If the pager can't be spawned in dispatch when --pager=on is
1094 1094 # given, don't try again when the command runs, to avoid a duplicate
1095 1095 # warning about a missing pager command.
1096 1096 self.disablepager()
1097 1097
1098 1098 def _runpager(self, command, env=None):
1099 1099 """Actually start the pager and set up file descriptors.
1100 1100
1101 1101 This is separate in part so that extensions (like chg) can
1102 1102 override how a pager is invoked.
1103 1103 """
1104 1104 if command == 'cat':
1105 1105 # Save ourselves some work.
1106 1106 return False
1107 1107 # If the command doesn't contain any of these characters, we
1108 1108 # assume it's a binary and exec it directly. This means for
1109 1109 # simple pager command configurations, we can degrade
1110 1110 # gracefully and tell the user about their broken pager.
1111 1111 shell = any(c in command for c in "|&;<>()$`\\\"' \t\n*?[#~=%")
1112 1112
1113 1113 if pycompat.iswindows and not shell:
1114 1114 # Window's built-in `more` cannot be invoked with shell=False, but
1115 1115 # its `more.com` can. Hide this implementation detail from the
1116 1116 # user so we can also get sane bad PAGER behavior. MSYS has
1117 1117 # `more.exe`, so do a cmd.exe style resolution of the executable to
1118 1118 # determine which one to use.
1119 1119 fullcmd = procutil.findexe(command)
1120 1120 if not fullcmd:
1121 1121 self.warn(_("missing pager command '%s', skipping pager\n")
1122 1122 % command)
1123 1123 return False
1124 1124
1125 1125 command = fullcmd
1126 1126
1127 1127 try:
1128 1128 pager = subprocess.Popen(
1129 1129 command, shell=shell, bufsize=-1,
1130 1130 close_fds=procutil.closefds, stdin=subprocess.PIPE,
1131 1131 stdout=procutil.stdout, stderr=procutil.stderr,
1132 1132 env=procutil.shellenviron(env))
1133 1133 except OSError as e:
1134 1134 if e.errno == errno.ENOENT and not shell:
1135 1135 self.warn(_("missing pager command '%s', skipping pager\n")
1136 1136 % command)
1137 1137 return False
1138 1138 raise
1139 1139
1140 1140 # back up original file descriptors
1141 1141 stdoutfd = os.dup(procutil.stdout.fileno())
1142 1142 stderrfd = os.dup(procutil.stderr.fileno())
1143 1143
1144 1144 os.dup2(pager.stdin.fileno(), procutil.stdout.fileno())
1145 1145 if self._isatty(procutil.stderr):
1146 1146 os.dup2(pager.stdin.fileno(), procutil.stderr.fileno())
1147 1147
1148 1148 @self.atexit
1149 1149 def killpager():
1150 1150 if util.safehasattr(signal, "SIGINT"):
1151 1151 signal.signal(signal.SIGINT, signal.SIG_IGN)
1152 1152 # restore original fds, closing pager.stdin copies in the process
1153 1153 os.dup2(stdoutfd, procutil.stdout.fileno())
1154 1154 os.dup2(stderrfd, procutil.stderr.fileno())
1155 1155 pager.stdin.close()
1156 1156 pager.wait()
1157 1157
1158 1158 return True
1159 1159
1160 1160 @property
1161 1161 def _exithandlers(self):
1162 1162 return _reqexithandlers
1163 1163
1164 1164 def atexit(self, func, *args, **kwargs):
1165 1165 '''register a function to run after dispatching a request
1166 1166
1167 1167 Handlers do not stay registered across request boundaries.'''
1168 1168 self._exithandlers.append((func, args, kwargs))
1169 1169 return func
1170 1170
1171 1171 def interface(self, feature):
1172 1172 """what interface to use for interactive console features?
1173 1173
1174 1174 The interface is controlled by the value of `ui.interface` but also by
1175 1175 the value of feature-specific configuration. For example:
1176 1176
1177 1177 ui.interface.histedit = text
1178 1178 ui.interface.chunkselector = curses
1179 1179
1180 1180 Here the features are "histedit" and "chunkselector".
1181 1181
1182 1182 The configuration above means that the default interfaces for commands
1183 1183 is curses, the interface for histedit is text and the interface for
1184 1184 selecting chunk is crecord (the best curses interface available).
1185 1185
1186 1186 Consider the following example:
1187 1187 ui.interface = curses
1188 1188 ui.interface.histedit = text
1189 1189
1190 1190 Then histedit will use the text interface and chunkselector will use
1191 1191 the default curses interface (crecord at the moment).
1192 1192 """
1193 1193 alldefaults = frozenset(["text", "curses"])
1194 1194
1195 1195 featureinterfaces = {
1196 1196 "chunkselector": [
1197 1197 "text",
1198 1198 "curses",
1199 1199 ]
1200 1200 }
1201 1201
1202 1202 # Feature-specific interface
1203 1203 if feature not in featureinterfaces.keys():
1204 1204 # Programming error, not user error
1205 1205 raise ValueError("Unknown feature requested %s" % feature)
1206 1206
1207 1207 availableinterfaces = frozenset(featureinterfaces[feature])
1208 1208 if alldefaults > availableinterfaces:
1209 1209 # Programming error, not user error. We need a use case to
1210 1210 # define the right thing to do here.
1211 1211 raise ValueError(
1212 1212 "Feature %s does not handle all default interfaces" %
1213 1213 feature)
1214 1214
1215 1215 if self.plain() or encoding.environ.get('TERM') == 'dumb':
1216 1216 return "text"
1217 1217
1218 1218 # Default interface for all the features
1219 1219 defaultinterface = "text"
1220 1220 i = self.config("ui", "interface")
1221 1221 if i in alldefaults:
1222 1222 defaultinterface = i
1223 1223
1224 1224 choseninterface = defaultinterface
1225 1225 f = self.config("ui", "interface.%s" % feature)
1226 1226 if f in availableinterfaces:
1227 1227 choseninterface = f
1228 1228
1229 1229 if i is not None and defaultinterface != i:
1230 1230 if f is not None:
1231 1231 self.warn(_("invalid value for ui.interface: %s\n") %
1232 1232 (i,))
1233 1233 else:
1234 1234 self.warn(_("invalid value for ui.interface: %s (using %s)\n") %
1235 1235 (i, choseninterface))
1236 1236 if f is not None and choseninterface != f:
1237 1237 self.warn(_("invalid value for ui.interface.%s: %s (using %s)\n") %
1238 1238 (feature, f, choseninterface))
1239 1239
1240 1240 return choseninterface
1241 1241
1242 1242 def interactive(self):
1243 1243 '''is interactive input allowed?
1244 1244
1245 1245 An interactive session is a session where input can be reasonably read
1246 1246 from `sys.stdin'. If this function returns false, any attempt to read
1247 1247 from stdin should fail with an error, unless a sensible default has been
1248 1248 specified.
1249 1249
1250 1250 Interactiveness is triggered by the value of the `ui.interactive'
1251 1251 configuration variable or - if it is unset - when `sys.stdin' points
1252 1252 to a terminal device.
1253 1253
1254 1254 This function refers to input only; for output, see `ui.formatted()'.
1255 1255 '''
1256 1256 i = self.configbool("ui", "interactive")
1257 1257 if i is None:
1258 1258 # some environments replace stdin without implementing isatty
1259 1259 # usually those are non-interactive
1260 1260 return self._isatty(self.fin)
1261 1261
1262 1262 return i
1263 1263
1264 1264 def termwidth(self):
1265 1265 '''how wide is the terminal in columns?
1266 1266 '''
1267 1267 if 'COLUMNS' in encoding.environ:
1268 1268 try:
1269 1269 return int(encoding.environ['COLUMNS'])
1270 1270 except ValueError:
1271 1271 pass
1272 1272 return scmutil.termsize(self)[0]
1273 1273
1274 1274 def formatted(self):
1275 1275 '''should formatted output be used?
1276 1276
1277 1277 It is often desirable to format the output to suite the output medium.
1278 1278 Examples of this are truncating long lines or colorizing messages.
1279 1279 However, this is not often not desirable when piping output into other
1280 1280 utilities, e.g. `grep'.
1281 1281
1282 1282 Formatted output is triggered by the value of the `ui.formatted'
1283 1283 configuration variable or - if it is unset - when `sys.stdout' points
1284 1284 to a terminal device. Please note that `ui.formatted' should be
1285 1285 considered an implementation detail; it is not intended for use outside
1286 1286 Mercurial or its extensions.
1287 1287
1288 1288 This function refers to output only; for input, see `ui.interactive()'.
1289 1289 This function always returns false when in plain mode, see `ui.plain()'.
1290 1290 '''
1291 1291 if self.plain():
1292 1292 return False
1293 1293
1294 1294 i = self.configbool("ui", "formatted")
1295 1295 if i is None:
1296 1296 # some environments replace stdout without implementing isatty
1297 1297 # usually those are non-interactive
1298 1298 return self._isatty(self.fout)
1299 1299
1300 1300 return i
1301 1301
1302 1302 def _readline(self):
1303 1303 # Replacing stdin/stdout temporarily is a hard problem on Python 3
1304 1304 # because they have to be text streams with *no buffering*. Instead,
1305 1305 # we use rawinput() only if call_readline() will be invoked by
1306 1306 # PyOS_Readline(), so no I/O will be made at Python layer.
1307 1307 usereadline = (self._isatty(self.fin) and self._isatty(self.fout)
1308 1308 and procutil.isstdin(self.fin)
1309 1309 and procutil.isstdout(self.fout))
1310 1310 if usereadline:
1311 1311 try:
1312 1312 # magically add command line editing support, where
1313 1313 # available
1314 1314 import readline
1315 1315 # force demandimport to really load the module
1316 1316 readline.read_history_file
1317 1317 # windows sometimes raises something other than ImportError
1318 1318 except Exception:
1319 1319 usereadline = False
1320 1320
1321 1321 # prompt ' ' must exist; otherwise readline may delete entire line
1322 1322 # - http://bugs.python.org/issue12833
1323 1323 with self.timeblockedsection('stdio'):
1324 1324 if usereadline:
1325 1325 line = encoding.strtolocal(pycompat.rawinput(r' '))
1326 1326 # When stdin is in binary mode on Windows, it can cause
1327 1327 # raw_input() to emit an extra trailing carriage return
1328 1328 if pycompat.oslinesep == b'\r\n' and line.endswith(b'\r'):
1329 1329 line = line[:-1]
1330 1330 else:
1331 1331 self.fout.write(b' ')
1332 1332 self.fout.flush()
1333 1333 line = self.fin.readline()
1334 1334 if not line:
1335 1335 raise EOFError
1336 1336 line = line.rstrip(pycompat.oslinesep)
1337 1337
1338 1338 return line
1339 1339
1340 1340 def prompt(self, msg, default="y"):
1341 1341 """Prompt user with msg, read response.
1342 1342 If ui is not interactive, the default is returned.
1343 1343 """
1344 1344 if not self.interactive():
1345 1345 self.write(msg, ' ', default or '', "\n")
1346 1346 return default
1347 1347 self._writenobuf(msg, label='ui.prompt')
1348 1348 self.flush()
1349 1349 try:
1350 1350 r = self._readline()
1351 1351 if not r:
1352 1352 r = default
1353 1353 if self.configbool('ui', 'promptecho'):
1354 1354 self.write(r, "\n")
1355 1355 return r
1356 1356 except EOFError:
1357 1357 raise error.ResponseExpected()
1358 1358
1359 1359 @staticmethod
1360 1360 def extractchoices(prompt):
1361 1361 """Extract prompt message and list of choices from specified prompt.
1362 1362
1363 1363 This returns tuple "(message, choices)", and "choices" is the
1364 1364 list of tuple "(response character, text without &)".
1365 1365
1366 1366 >>> ui.extractchoices(b"awake? $$ &Yes $$ &No")
1367 1367 ('awake? ', [('y', 'Yes'), ('n', 'No')])
1368 1368 >>> ui.extractchoices(b"line\\nbreak? $$ &Yes $$ &No")
1369 1369 ('line\\nbreak? ', [('y', 'Yes'), ('n', 'No')])
1370 1370 >>> ui.extractchoices(b"want lots of $$money$$?$$Ye&s$$N&o")
1371 1371 ('want lots of $$money$$?', [('s', 'Yes'), ('o', 'No')])
1372 1372 """
1373 1373
1374 1374 # Sadly, the prompt string may have been built with a filename
1375 1375 # containing "$$" so let's try to find the first valid-looking
1376 1376 # prompt to start parsing. Sadly, we also can't rely on
1377 1377 # choices containing spaces, ASCII, or basically anything
1378 1378 # except an ampersand followed by a character.
1379 1379 m = re.match(br'(?s)(.+?)\$\$([^\$]*&[^ \$].*)', prompt)
1380 1380 msg = m.group(1)
1381 1381 choices = [p.strip(' ') for p in m.group(2).split('$$')]
1382 1382 def choicetuple(s):
1383 1383 ampidx = s.index('&')
1384 1384 return s[ampidx + 1:ampidx + 2].lower(), s.replace('&', '', 1)
1385 1385 return (msg, [choicetuple(s) for s in choices])
1386 1386
1387 1387 def promptchoice(self, prompt, default=0):
1388 1388 """Prompt user with a message, read response, and ensure it matches
1389 1389 one of the provided choices. The prompt is formatted as follows:
1390 1390
1391 1391 "would you like fries with that (Yn)? $$ &Yes $$ &No"
1392 1392
1393 1393 The index of the choice is returned. Responses are case
1394 1394 insensitive. If ui is not interactive, the default is
1395 1395 returned.
1396 1396 """
1397 1397
1398 1398 msg, choices = self.extractchoices(prompt)
1399 1399 resps = [r for r, t in choices]
1400 1400 while True:
1401 1401 r = self.prompt(msg, resps[default])
1402 1402 if r.lower() in resps:
1403 1403 return resps.index(r.lower())
1404 1404 self.write(_("unrecognized response\n"))
1405 1405
1406 1406 def getpass(self, prompt=None, default=None):
1407 1407 if not self.interactive():
1408 1408 return default
1409 1409 try:
1410 1410 self.write_err(self.label(prompt or _('password: '), 'ui.prompt'))
1411 1411 # disable getpass() only if explicitly specified. it's still valid
1412 1412 # to interact with tty even if fin is not a tty.
1413 1413 with self.timeblockedsection('stdio'):
1414 1414 if self.configbool('ui', 'nontty'):
1415 1415 l = self.fin.readline()
1416 1416 if not l:
1417 1417 raise EOFError
1418 1418 return l.rstrip('\n')
1419 1419 else:
1420 1420 return getpass.getpass('')
1421 1421 except EOFError:
1422 1422 raise error.ResponseExpected()
1423
1423 1424 def status(self, *msg, **opts):
1424 1425 '''write status message to output (if ui.quiet is False)
1425 1426
1426 1427 This adds an output label of "ui.status".
1427 1428 '''
1428 1429 if not self.quiet:
1429 1430 opts[r'label'] = opts.get(r'label', '') + ' ui.status'
1430 1431 self.write(*msg, **opts)
1432
1431 1433 def warn(self, *msg, **opts):
1432 1434 '''write warning message to output (stderr)
1433 1435
1434 1436 This adds an output label of "ui.warning".
1435 1437 '''
1436 1438 opts[r'label'] = opts.get(r'label', '') + ' ui.warning'
1437 1439 self.write_err(*msg, **opts)
1440
1441 def error(self, *msg, **opts):
1442 '''write error message to output (stderr)
1443
1444 This adds an output label of "ui.error".
1445 '''
1446 opts[r'label'] = opts.get(r'label', '') + ' ui.error'
1447 self.write_err(*msg, **opts)
1448
1438 1449 def note(self, *msg, **opts):
1439 1450 '''write note to output (if ui.verbose is True)
1440 1451
1441 1452 This adds an output label of "ui.note".
1442 1453 '''
1443 1454 if self.verbose:
1444 1455 opts[r'label'] = opts.get(r'label', '') + ' ui.note'
1445 1456 self.write(*msg, **opts)
1457
1446 1458 def debug(self, *msg, **opts):
1447 1459 '''write debug message to output (if ui.debugflag is True)
1448 1460
1449 1461 This adds an output label of "ui.debug".
1450 1462 '''
1451 1463 if self.debugflag:
1452 1464 opts[r'label'] = opts.get(r'label', '') + ' ui.debug'
1453 1465 self.write(*msg, **opts)
1454 1466
1455 1467 def edit(self, text, user, extra=None, editform=None, pending=None,
1456 1468 repopath=None, action=None):
1457 1469 if action is None:
1458 1470 self.develwarn('action is None but will soon be a required '
1459 1471 'parameter to ui.edit()')
1460 1472 extra_defaults = {
1461 1473 'prefix': 'editor',
1462 1474 'suffix': '.txt',
1463 1475 }
1464 1476 if extra is not None:
1465 1477 if extra.get('suffix') is not None:
1466 1478 self.develwarn('extra.suffix is not None but will soon be '
1467 1479 'ignored by ui.edit()')
1468 1480 extra_defaults.update(extra)
1469 1481 extra = extra_defaults
1470 1482
1471 1483 if action == 'diff':
1472 1484 suffix = '.diff'
1473 1485 elif action:
1474 1486 suffix = '.%s.hg.txt' % action
1475 1487 else:
1476 1488 suffix = extra['suffix']
1477 1489
1478 1490 rdir = None
1479 1491 if self.configbool('experimental', 'editortmpinhg'):
1480 1492 rdir = repopath
1481 1493 (fd, name) = pycompat.mkstemp(prefix='hg-' + extra['prefix'] + '-',
1482 1494 suffix=suffix,
1483 1495 dir=rdir)
1484 1496 try:
1485 1497 f = os.fdopen(fd, r'wb')
1486 1498 f.write(util.tonativeeol(text))
1487 1499 f.close()
1488 1500
1489 1501 environ = {'HGUSER': user}
1490 1502 if 'transplant_source' in extra:
1491 1503 environ.update({'HGREVISION': hex(extra['transplant_source'])})
1492 1504 for label in ('intermediate-source', 'source', 'rebase_source'):
1493 1505 if label in extra:
1494 1506 environ.update({'HGREVISION': extra[label]})
1495 1507 break
1496 1508 if editform:
1497 1509 environ.update({'HGEDITFORM': editform})
1498 1510 if pending:
1499 1511 environ.update({'HG_PENDING': pending})
1500 1512
1501 1513 editor = self.geteditor()
1502 1514
1503 1515 self.system("%s \"%s\"" % (editor, name),
1504 1516 environ=environ,
1505 1517 onerr=error.Abort, errprefix=_("edit failed"),
1506 1518 blockedtag='editor')
1507 1519
1508 1520 f = open(name, r'rb')
1509 1521 t = util.fromnativeeol(f.read())
1510 1522 f.close()
1511 1523 finally:
1512 1524 os.unlink(name)
1513 1525
1514 1526 return t
1515 1527
1516 1528 def system(self, cmd, environ=None, cwd=None, onerr=None, errprefix=None,
1517 1529 blockedtag=None):
1518 1530 '''execute shell command with appropriate output stream. command
1519 1531 output will be redirected if fout is not stdout.
1520 1532
1521 1533 if command fails and onerr is None, return status, else raise onerr
1522 1534 object as exception.
1523 1535 '''
1524 1536 if blockedtag is None:
1525 1537 # Long cmds tend to be because of an absolute path on cmd. Keep
1526 1538 # the tail end instead
1527 1539 cmdsuffix = cmd.translate(None, _keepalnum)[-85:]
1528 1540 blockedtag = 'unknown_system_' + cmdsuffix
1529 1541 out = self.fout
1530 1542 if any(s[1] for s in self._bufferstates):
1531 1543 out = self
1532 1544 with self.timeblockedsection(blockedtag):
1533 1545 rc = self._runsystem(cmd, environ=environ, cwd=cwd, out=out)
1534 1546 if rc and onerr:
1535 1547 errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]),
1536 1548 procutil.explainexit(rc))
1537 1549 if errprefix:
1538 1550 errmsg = '%s: %s' % (errprefix, errmsg)
1539 1551 raise onerr(errmsg)
1540 1552 return rc
1541 1553
1542 1554 def _runsystem(self, cmd, environ, cwd, out):
1543 1555 """actually execute the given shell command (can be overridden by
1544 1556 extensions like chg)"""
1545 1557 return procutil.system(cmd, environ=environ, cwd=cwd, out=out)
1546 1558
1547 1559 def traceback(self, exc=None, force=False):
1548 1560 '''print exception traceback if traceback printing enabled or forced.
1549 1561 only to call in exception handler. returns true if traceback
1550 1562 printed.'''
1551 1563 if self.tracebackflag or force:
1552 1564 if exc is None:
1553 1565 exc = sys.exc_info()
1554 1566 cause = getattr(exc[1], 'cause', None)
1555 1567
1556 1568 if cause is not None:
1557 1569 causetb = traceback.format_tb(cause[2])
1558 1570 exctb = traceback.format_tb(exc[2])
1559 1571 exconly = traceback.format_exception_only(cause[0], cause[1])
1560 1572
1561 1573 # exclude frame where 'exc' was chained and rethrown from exctb
1562 1574 self.write_err('Traceback (most recent call last):\n',
1563 1575 ''.join(exctb[:-1]),
1564 1576 ''.join(causetb),
1565 1577 ''.join(exconly))
1566 1578 else:
1567 1579 output = traceback.format_exception(exc[0], exc[1], exc[2])
1568 1580 self.write_err(encoding.strtolocal(r''.join(output)))
1569 1581 return self.tracebackflag or force
1570 1582
1571 1583 def geteditor(self):
1572 1584 '''return editor to use'''
1573 1585 if pycompat.sysplatform == 'plan9':
1574 1586 # vi is the MIPS instruction simulator on Plan 9. We
1575 1587 # instead default to E to plumb commit messages to
1576 1588 # avoid confusion.
1577 1589 editor = 'E'
1578 1590 else:
1579 1591 editor = 'vi'
1580 1592 return (encoding.environ.get("HGEDITOR") or
1581 1593 self.config("ui", "editor", editor))
1582 1594
1583 1595 @util.propertycache
1584 1596 def _progbar(self):
1585 1597 """setup the progbar singleton to the ui object"""
1586 1598 if (self.quiet or self.debugflag
1587 1599 or self.configbool('progress', 'disable')
1588 1600 or not progress.shouldprint(self)):
1589 1601 return None
1590 1602 return getprogbar(self)
1591 1603
1592 1604 def _progclear(self):
1593 1605 """clear progress bar output if any. use it before any output"""
1594 1606 if not haveprogbar(): # nothing loaded yet
1595 1607 return
1596 1608 if self._progbar is not None and self._progbar.printed:
1597 1609 self._progbar.clear()
1598 1610
1599 1611 def progress(self, topic, pos, item="", unit="", total=None):
1600 1612 '''show a progress message
1601 1613
1602 1614 By default a textual progress bar will be displayed if an operation
1603 1615 takes too long. 'topic' is the current operation, 'item' is a
1604 1616 non-numeric marker of the current position (i.e. the currently
1605 1617 in-process file), 'pos' is the current numeric position (i.e.
1606 1618 revision, bytes, etc.), unit is a corresponding unit label,
1607 1619 and total is the highest expected pos.
1608 1620
1609 1621 Multiple nested topics may be active at a time.
1610 1622
1611 1623 All topics should be marked closed by setting pos to None at
1612 1624 termination.
1613 1625 '''
1614 1626 if self._progbar is not None:
1615 1627 self._progbar.progress(topic, pos, item=item, unit=unit,
1616 1628 total=total)
1617 1629 if pos is None or not self.configbool('progress', 'debug'):
1618 1630 return
1619 1631
1620 1632 if unit:
1621 1633 unit = ' ' + unit
1622 1634 if item:
1623 1635 item = ' ' + item
1624 1636
1625 1637 if total:
1626 1638 pct = 100.0 * pos / total
1627 1639 self.debug('%s:%s %d/%d%s (%4.2f%%)\n'
1628 1640 % (topic, item, pos, total, unit, pct))
1629 1641 else:
1630 1642 self.debug('%s:%s %d%s\n' % (topic, item, pos, unit))
1631 1643
1632 1644 def makeprogress(self, topic, unit="", total=None):
1633 1645 '''exists only so low-level modules won't need to import scmutil'''
1634 1646 return scmutil.progress(self, topic, unit, total)
1635 1647
1636 1648 def log(self, service, *msg, **opts):
1637 1649 '''hook for logging facility extensions
1638 1650
1639 1651 service should be a readily-identifiable subsystem, which will
1640 1652 allow filtering.
1641 1653
1642 1654 *msg should be a newline-terminated format string to log, and
1643 1655 then any values to %-format into that format string.
1644 1656
1645 1657 **opts currently has no defined meanings.
1646 1658 '''
1647 1659
1648 1660 def label(self, msg, label):
1649 1661 '''style msg based on supplied label
1650 1662
1651 1663 If some color mode is enabled, this will add the necessary control
1652 1664 characters to apply such color. In addition, 'debug' color mode adds
1653 1665 markup showing which label affects a piece of text.
1654 1666
1655 1667 ui.write(s, 'label') is equivalent to
1656 1668 ui.write(ui.label(s, 'label')).
1657 1669 '''
1658 1670 if self._colormode is not None:
1659 1671 return color.colorlabel(self, msg, label)
1660 1672 return msg
1661 1673
1662 1674 def develwarn(self, msg, stacklevel=1, config=None):
1663 1675 """issue a developer warning message
1664 1676
1665 1677 Use 'stacklevel' to report the offender some layers further up in the
1666 1678 stack.
1667 1679 """
1668 1680 if not self.configbool('devel', 'all-warnings'):
1669 1681 if config is None or not self.configbool('devel', config):
1670 1682 return
1671 1683 msg = 'devel-warn: ' + msg
1672 1684 stacklevel += 1 # get in develwarn
1673 1685 if self.tracebackflag:
1674 1686 util.debugstacktrace(msg, stacklevel, self.ferr, self.fout)
1675 1687 self.log('develwarn', '%s at:\n%s' %
1676 1688 (msg, ''.join(util.getstackframes(stacklevel))))
1677 1689 else:
1678 1690 curframe = inspect.currentframe()
1679 1691 calframe = inspect.getouterframes(curframe, 2)
1680 1692 fname, lineno, fmsg = calframe[stacklevel][1:4]
1681 1693 fname, fmsg = pycompat.sysbytes(fname), pycompat.sysbytes(fmsg)
1682 1694 self.write_err('%s at: %s:%d (%s)\n'
1683 1695 % (msg, fname, lineno, fmsg))
1684 1696 self.log('develwarn', '%s at: %s:%d (%s)\n',
1685 1697 msg, fname, lineno, fmsg)
1686 1698 curframe = calframe = None # avoid cycles
1687 1699
1688 1700 def deprecwarn(self, msg, version, stacklevel=2):
1689 1701 """issue a deprecation warning
1690 1702
1691 1703 - msg: message explaining what is deprecated and how to upgrade,
1692 1704 - version: last version where the API will be supported,
1693 1705 """
1694 1706 if not (self.configbool('devel', 'all-warnings')
1695 1707 or self.configbool('devel', 'deprec-warn')):
1696 1708 return
1697 1709 msg += ("\n(compatibility will be dropped after Mercurial-%s,"
1698 1710 " update your code.)") % version
1699 1711 self.develwarn(msg, stacklevel=stacklevel, config='deprec-warn')
1700 1712
1701 1713 def exportableenviron(self):
1702 1714 """The environment variables that are safe to export, e.g. through
1703 1715 hgweb.
1704 1716 """
1705 1717 return self._exportableenviron
1706 1718
1707 1719 @contextlib.contextmanager
1708 1720 def configoverride(self, overrides, source=""):
1709 1721 """Context manager for temporary config overrides
1710 1722 `overrides` must be a dict of the following structure:
1711 1723 {(section, name) : value}"""
1712 1724 backups = {}
1713 1725 try:
1714 1726 for (section, name), value in overrides.items():
1715 1727 backups[(section, name)] = self.backupconfig(section, name)
1716 1728 self.setconfig(section, name, value, source)
1717 1729 yield
1718 1730 finally:
1719 1731 for __, backup in backups.items():
1720 1732 self.restoreconfig(backup)
1721 1733 # just restoring ui.quiet config to the previous value is not enough
1722 1734 # as it does not update ui.quiet class member
1723 1735 if ('ui', 'quiet') in overrides:
1724 1736 self.fixconfig(section='ui')
1725 1737
1726 1738 class paths(dict):
1727 1739 """Represents a collection of paths and their configs.
1728 1740
1729 1741 Data is initially derived from ui instances and the config files they have
1730 1742 loaded.
1731 1743 """
1732 1744 def __init__(self, ui):
1733 1745 dict.__init__(self)
1734 1746
1735 1747 for name, loc in ui.configitems('paths', ignoresub=True):
1736 1748 # No location is the same as not existing.
1737 1749 if not loc:
1738 1750 continue
1739 1751 loc, sub = ui.configsuboptions('paths', name)
1740 1752 self[name] = path(ui, name, rawloc=loc, suboptions=sub)
1741 1753
1742 1754 def getpath(self, name, default=None):
1743 1755 """Return a ``path`` from a string, falling back to default.
1744 1756
1745 1757 ``name`` can be a named path or locations. Locations are filesystem
1746 1758 paths or URIs.
1747 1759
1748 1760 Returns None if ``name`` is not a registered path, a URI, or a local
1749 1761 path to a repo.
1750 1762 """
1751 1763 # Only fall back to default if no path was requested.
1752 1764 if name is None:
1753 1765 if not default:
1754 1766 default = ()
1755 1767 elif not isinstance(default, (tuple, list)):
1756 1768 default = (default,)
1757 1769 for k in default:
1758 1770 try:
1759 1771 return self[k]
1760 1772 except KeyError:
1761 1773 continue
1762 1774 return None
1763 1775
1764 1776 # Most likely empty string.
1765 1777 # This may need to raise in the future.
1766 1778 if not name:
1767 1779 return None
1768 1780
1769 1781 try:
1770 1782 return self[name]
1771 1783 except KeyError:
1772 1784 # Try to resolve as a local path or URI.
1773 1785 try:
1774 1786 # We don't pass sub-options in, so no need to pass ui instance.
1775 1787 return path(None, None, rawloc=name)
1776 1788 except ValueError:
1777 1789 raise error.RepoError(_('repository %s does not exist') %
1778 1790 name)
1779 1791
1780 1792 _pathsuboptions = {}
1781 1793
1782 1794 def pathsuboption(option, attr):
1783 1795 """Decorator used to declare a path sub-option.
1784 1796
1785 1797 Arguments are the sub-option name and the attribute it should set on
1786 1798 ``path`` instances.
1787 1799
1788 1800 The decorated function will receive as arguments a ``ui`` instance,
1789 1801 ``path`` instance, and the string value of this option from the config.
1790 1802 The function should return the value that will be set on the ``path``
1791 1803 instance.
1792 1804
1793 1805 This decorator can be used to perform additional verification of
1794 1806 sub-options and to change the type of sub-options.
1795 1807 """
1796 1808 def register(func):
1797 1809 _pathsuboptions[option] = (attr, func)
1798 1810 return func
1799 1811 return register
1800 1812
1801 1813 @pathsuboption('pushurl', 'pushloc')
1802 1814 def pushurlpathoption(ui, path, value):
1803 1815 u = util.url(value)
1804 1816 # Actually require a URL.
1805 1817 if not u.scheme:
1806 1818 ui.warn(_('(paths.%s:pushurl not a URL; ignoring)\n') % path.name)
1807 1819 return None
1808 1820
1809 1821 # Don't support the #foo syntax in the push URL to declare branch to
1810 1822 # push.
1811 1823 if u.fragment:
1812 1824 ui.warn(_('("#fragment" in paths.%s:pushurl not supported; '
1813 1825 'ignoring)\n') % path.name)
1814 1826 u.fragment = None
1815 1827
1816 1828 return bytes(u)
1817 1829
1818 1830 @pathsuboption('pushrev', 'pushrev')
1819 1831 def pushrevpathoption(ui, path, value):
1820 1832 return value
1821 1833
1822 1834 class path(object):
1823 1835 """Represents an individual path and its configuration."""
1824 1836
1825 1837 def __init__(self, ui, name, rawloc=None, suboptions=None):
1826 1838 """Construct a path from its config options.
1827 1839
1828 1840 ``ui`` is the ``ui`` instance the path is coming from.
1829 1841 ``name`` is the symbolic name of the path.
1830 1842 ``rawloc`` is the raw location, as defined in the config.
1831 1843 ``pushloc`` is the raw locations pushes should be made to.
1832 1844
1833 1845 If ``name`` is not defined, we require that the location be a) a local
1834 1846 filesystem path with a .hg directory or b) a URL. If not,
1835 1847 ``ValueError`` is raised.
1836 1848 """
1837 1849 if not rawloc:
1838 1850 raise ValueError('rawloc must be defined')
1839 1851
1840 1852 # Locations may define branches via syntax <base>#<branch>.
1841 1853 u = util.url(rawloc)
1842 1854 branch = None
1843 1855 if u.fragment:
1844 1856 branch = u.fragment
1845 1857 u.fragment = None
1846 1858
1847 1859 self.url = u
1848 1860 self.branch = branch
1849 1861
1850 1862 self.name = name
1851 1863 self.rawloc = rawloc
1852 1864 self.loc = '%s' % u
1853 1865
1854 1866 # When given a raw location but not a symbolic name, validate the
1855 1867 # location is valid.
1856 1868 if not name and not u.scheme and not self._isvalidlocalpath(self.loc):
1857 1869 raise ValueError('location is not a URL or path to a local '
1858 1870 'repo: %s' % rawloc)
1859 1871
1860 1872 suboptions = suboptions or {}
1861 1873
1862 1874 # Now process the sub-options. If a sub-option is registered, its
1863 1875 # attribute will always be present. The value will be None if there
1864 1876 # was no valid sub-option.
1865 1877 for suboption, (attr, func) in _pathsuboptions.iteritems():
1866 1878 if suboption not in suboptions:
1867 1879 setattr(self, attr, None)
1868 1880 continue
1869 1881
1870 1882 value = func(ui, self, suboptions[suboption])
1871 1883 setattr(self, attr, value)
1872 1884
1873 1885 def _isvalidlocalpath(self, path):
1874 1886 """Returns True if the given path is a potentially valid repository.
1875 1887 This is its own function so that extensions can change the definition of
1876 1888 'valid' in this case (like when pulling from a git repo into a hg
1877 1889 one)."""
1878 1890 return os.path.isdir(os.path.join(path, '.hg'))
1879 1891
1880 1892 @property
1881 1893 def suboptions(self):
1882 1894 """Return sub-options and their values for this path.
1883 1895
1884 1896 This is intended to be used for presentation purposes.
1885 1897 """
1886 1898 d = {}
1887 1899 for subopt, (attr, _func) in _pathsuboptions.iteritems():
1888 1900 value = getattr(self, attr)
1889 1901 if value is not None:
1890 1902 d[subopt] = value
1891 1903 return d
1892 1904
1893 1905 # we instantiate one globally shared progress bar to avoid
1894 1906 # competing progress bars when multiple UI objects get created
1895 1907 _progresssingleton = None
1896 1908
1897 1909 def getprogbar(ui):
1898 1910 global _progresssingleton
1899 1911 if _progresssingleton is None:
1900 1912 # passing 'ui' object to the singleton is fishy,
1901 1913 # this is how the extension used to work but feel free to rework it.
1902 1914 _progresssingleton = progress.progbar(ui)
1903 1915 return _progresssingleton
1904 1916
1905 1917 def haveprogbar():
1906 1918 return _progresssingleton is not None
General Comments 0
You need to be logged in to leave comments. Login now