##// 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 # utility for color output for Mercurial commands
1 # utility for color output for Mercurial commands
2 #
2 #
3 # Copyright (C) 2007 Kevin Christen <kevin.christen@gmail.com> and other
3 # Copyright (C) 2007 Kevin Christen <kevin.christen@gmail.com> and other
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import re
10 import re
11
11
12 from .i18n import _
12 from .i18n import _
13
13
14 from . import (
14 from . import (
15 encoding,
15 encoding,
16 pycompat,
16 pycompat,
17 )
17 )
18
18
19 from .utils import (
19 from .utils import (
20 stringutil,
20 stringutil,
21 )
21 )
22
22
23 try:
23 try:
24 import curses
24 import curses
25 # Mapping from effect name to terminfo attribute name (or raw code) or
25 # Mapping from effect name to terminfo attribute name (or raw code) or
26 # color number. This will also force-load the curses module.
26 # color number. This will also force-load the curses module.
27 _baseterminfoparams = {
27 _baseterminfoparams = {
28 'none': (True, 'sgr0', ''),
28 'none': (True, 'sgr0', ''),
29 'standout': (True, 'smso', ''),
29 'standout': (True, 'smso', ''),
30 'underline': (True, 'smul', ''),
30 'underline': (True, 'smul', ''),
31 'reverse': (True, 'rev', ''),
31 'reverse': (True, 'rev', ''),
32 'inverse': (True, 'rev', ''),
32 'inverse': (True, 'rev', ''),
33 'blink': (True, 'blink', ''),
33 'blink': (True, 'blink', ''),
34 'dim': (True, 'dim', ''),
34 'dim': (True, 'dim', ''),
35 'bold': (True, 'bold', ''),
35 'bold': (True, 'bold', ''),
36 'invisible': (True, 'invis', ''),
36 'invisible': (True, 'invis', ''),
37 'italic': (True, 'sitm', ''),
37 'italic': (True, 'sitm', ''),
38 'black': (False, curses.COLOR_BLACK, ''),
38 'black': (False, curses.COLOR_BLACK, ''),
39 'red': (False, curses.COLOR_RED, ''),
39 'red': (False, curses.COLOR_RED, ''),
40 'green': (False, curses.COLOR_GREEN, ''),
40 'green': (False, curses.COLOR_GREEN, ''),
41 'yellow': (False, curses.COLOR_YELLOW, ''),
41 'yellow': (False, curses.COLOR_YELLOW, ''),
42 'blue': (False, curses.COLOR_BLUE, ''),
42 'blue': (False, curses.COLOR_BLUE, ''),
43 'magenta': (False, curses.COLOR_MAGENTA, ''),
43 'magenta': (False, curses.COLOR_MAGENTA, ''),
44 'cyan': (False, curses.COLOR_CYAN, ''),
44 'cyan': (False, curses.COLOR_CYAN, ''),
45 'white': (False, curses.COLOR_WHITE, ''),
45 'white': (False, curses.COLOR_WHITE, ''),
46 }
46 }
47 except ImportError:
47 except ImportError:
48 curses = None
48 curses = None
49 _baseterminfoparams = {}
49 _baseterminfoparams = {}
50
50
51 # start and stop parameters for effects
51 # start and stop parameters for effects
52 _effects = {
52 _effects = {
53 'none': 0,
53 'none': 0,
54 'black': 30,
54 'black': 30,
55 'red': 31,
55 'red': 31,
56 'green': 32,
56 'green': 32,
57 'yellow': 33,
57 'yellow': 33,
58 'blue': 34,
58 'blue': 34,
59 'magenta': 35,
59 'magenta': 35,
60 'cyan': 36,
60 'cyan': 36,
61 'white': 37,
61 'white': 37,
62 'bold': 1,
62 'bold': 1,
63 'italic': 3,
63 'italic': 3,
64 'underline': 4,
64 'underline': 4,
65 'inverse': 7,
65 'inverse': 7,
66 'dim': 2,
66 'dim': 2,
67 'black_background': 40,
67 'black_background': 40,
68 'red_background': 41,
68 'red_background': 41,
69 'green_background': 42,
69 'green_background': 42,
70 'yellow_background': 43,
70 'yellow_background': 43,
71 'blue_background': 44,
71 'blue_background': 44,
72 'purple_background': 45,
72 'purple_background': 45,
73 'cyan_background': 46,
73 'cyan_background': 46,
74 'white_background': 47,
74 'white_background': 47,
75 }
75 }
76
76
77 _defaultstyles = {
77 _defaultstyles = {
78 'grep.match': 'red bold',
78 'grep.match': 'red bold',
79 'grep.linenumber': 'green',
79 'grep.linenumber': 'green',
80 'grep.rev': 'green',
80 'grep.rev': 'green',
81 'grep.change': 'green',
81 'grep.change': 'green',
82 'grep.sep': 'cyan',
82 'grep.sep': 'cyan',
83 'grep.filename': 'magenta',
83 'grep.filename': 'magenta',
84 'grep.user': 'magenta',
84 'grep.user': 'magenta',
85 'grep.date': 'magenta',
85 'grep.date': 'magenta',
86 'bookmarks.active': 'green',
86 'bookmarks.active': 'green',
87 'branches.active': 'none',
87 'branches.active': 'none',
88 'branches.closed': 'black bold',
88 'branches.closed': 'black bold',
89 'branches.current': 'green',
89 'branches.current': 'green',
90 'branches.inactive': 'none',
90 'branches.inactive': 'none',
91 'diff.changed': 'white',
91 'diff.changed': 'white',
92 'diff.deleted': 'red',
92 'diff.deleted': 'red',
93 'diff.deleted.changed': 'red bold underline',
93 'diff.deleted.changed': 'red bold underline',
94 'diff.deleted.unchanged': 'red',
94 'diff.deleted.unchanged': 'red',
95 'diff.diffline': 'bold',
95 'diff.diffline': 'bold',
96 'diff.extended': 'cyan bold',
96 'diff.extended': 'cyan bold',
97 'diff.file_a': 'red bold',
97 'diff.file_a': 'red bold',
98 'diff.file_b': 'green bold',
98 'diff.file_b': 'green bold',
99 'diff.hunk': 'magenta',
99 'diff.hunk': 'magenta',
100 'diff.inserted': 'green',
100 'diff.inserted': 'green',
101 'diff.inserted.changed': 'green bold underline',
101 'diff.inserted.changed': 'green bold underline',
102 'diff.inserted.unchanged': 'green',
102 'diff.inserted.unchanged': 'green',
103 'diff.tab': '',
103 'diff.tab': '',
104 'diff.trailingwhitespace': 'bold red_background',
104 'diff.trailingwhitespace': 'bold red_background',
105 'changeset.public': '',
105 'changeset.public': '',
106 'changeset.draft': '',
106 'changeset.draft': '',
107 'changeset.secret': '',
107 'changeset.secret': '',
108 'diffstat.deleted': 'red',
108 'diffstat.deleted': 'red',
109 'diffstat.inserted': 'green',
109 'diffstat.inserted': 'green',
110 'formatvariant.name.mismatchconfig': 'red',
110 'formatvariant.name.mismatchconfig': 'red',
111 'formatvariant.name.mismatchdefault': 'yellow',
111 'formatvariant.name.mismatchdefault': 'yellow',
112 'formatvariant.name.uptodate': 'green',
112 'formatvariant.name.uptodate': 'green',
113 'formatvariant.repo.mismatchconfig': 'red',
113 'formatvariant.repo.mismatchconfig': 'red',
114 'formatvariant.repo.mismatchdefault': 'yellow',
114 'formatvariant.repo.mismatchdefault': 'yellow',
115 'formatvariant.repo.uptodate': 'green',
115 'formatvariant.repo.uptodate': 'green',
116 'formatvariant.config.special': 'yellow',
116 'formatvariant.config.special': 'yellow',
117 'formatvariant.config.default': 'green',
117 'formatvariant.config.default': 'green',
118 'formatvariant.default': '',
118 'formatvariant.default': '',
119 'histedit.remaining': 'red bold',
119 'histedit.remaining': 'red bold',
120 'ui.error': 'red',
120 'ui.prompt': 'yellow',
121 'ui.prompt': 'yellow',
121 'log.changeset': 'yellow',
122 'log.changeset': 'yellow',
122 'patchbomb.finalsummary': '',
123 'patchbomb.finalsummary': '',
123 'patchbomb.from': 'magenta',
124 'patchbomb.from': 'magenta',
124 'patchbomb.to': 'cyan',
125 'patchbomb.to': 'cyan',
125 'patchbomb.subject': 'green',
126 'patchbomb.subject': 'green',
126 'patchbomb.diffstats': '',
127 'patchbomb.diffstats': '',
127 'rebase.rebased': 'blue',
128 'rebase.rebased': 'blue',
128 'rebase.remaining': 'red bold',
129 'rebase.remaining': 'red bold',
129 'resolve.resolved': 'green bold',
130 'resolve.resolved': 'green bold',
130 'resolve.unresolved': 'red bold',
131 'resolve.unresolved': 'red bold',
131 'shelve.age': 'cyan',
132 'shelve.age': 'cyan',
132 'shelve.newest': 'green bold',
133 'shelve.newest': 'green bold',
133 'shelve.name': 'blue bold',
134 'shelve.name': 'blue bold',
134 'status.added': 'green bold',
135 'status.added': 'green bold',
135 'status.clean': 'none',
136 'status.clean': 'none',
136 'status.copied': 'none',
137 'status.copied': 'none',
137 'status.deleted': 'cyan bold underline',
138 'status.deleted': 'cyan bold underline',
138 'status.ignored': 'black bold',
139 'status.ignored': 'black bold',
139 'status.modified': 'blue bold',
140 'status.modified': 'blue bold',
140 'status.removed': 'red bold',
141 'status.removed': 'red bold',
141 'status.unknown': 'magenta bold underline',
142 'status.unknown': 'magenta bold underline',
142 'tags.normal': 'green',
143 'tags.normal': 'green',
143 'tags.local': 'black bold',
144 'tags.local': 'black bold',
144 }
145 }
145
146
146 def loadcolortable(ui, extname, colortable):
147 def loadcolortable(ui, extname, colortable):
147 _defaultstyles.update(colortable)
148 _defaultstyles.update(colortable)
148
149
149 def _terminfosetup(ui, mode, formatted):
150 def _terminfosetup(ui, mode, formatted):
150 '''Initialize terminfo data and the terminal if we're in terminfo mode.'''
151 '''Initialize terminfo data and the terminal if we're in terminfo mode.'''
151
152
152 # If we failed to load curses, we go ahead and return.
153 # If we failed to load curses, we go ahead and return.
153 if curses is None:
154 if curses is None:
154 return
155 return
155 # Otherwise, see what the config file says.
156 # Otherwise, see what the config file says.
156 if mode not in ('auto', 'terminfo'):
157 if mode not in ('auto', 'terminfo'):
157 return
158 return
158 ui._terminfoparams.update(_baseterminfoparams)
159 ui._terminfoparams.update(_baseterminfoparams)
159
160
160 for key, val in ui.configitems('color'):
161 for key, val in ui.configitems('color'):
161 if key.startswith('color.'):
162 if key.startswith('color.'):
162 newval = (False, int(val), '')
163 newval = (False, int(val), '')
163 ui._terminfoparams[key[6:]] = newval
164 ui._terminfoparams[key[6:]] = newval
164 elif key.startswith('terminfo.'):
165 elif key.startswith('terminfo.'):
165 newval = (True, '', val.replace('\\E', '\x1b'))
166 newval = (True, '', val.replace('\\E', '\x1b'))
166 ui._terminfoparams[key[9:]] = newval
167 ui._terminfoparams[key[9:]] = newval
167 try:
168 try:
168 curses.setupterm()
169 curses.setupterm()
169 except curses.error as e:
170 except curses.error as e:
170 ui._terminfoparams.clear()
171 ui._terminfoparams.clear()
171 return
172 return
172
173
173 for key, (b, e, c) in ui._terminfoparams.copy().items():
174 for key, (b, e, c) in ui._terminfoparams.copy().items():
174 if not b:
175 if not b:
175 continue
176 continue
176 if not c and not curses.tigetstr(pycompat.sysstr(e)):
177 if not c and not curses.tigetstr(pycompat.sysstr(e)):
177 # Most terminals don't support dim, invis, etc, so don't be
178 # Most terminals don't support dim, invis, etc, so don't be
178 # noisy and use ui.debug().
179 # noisy and use ui.debug().
179 ui.debug("no terminfo entry for %s\n" % e)
180 ui.debug("no terminfo entry for %s\n" % e)
180 del ui._terminfoparams[key]
181 del ui._terminfoparams[key]
181 if not curses.tigetstr(r'setaf') or not curses.tigetstr(r'setab'):
182 if not curses.tigetstr(r'setaf') or not curses.tigetstr(r'setab'):
182 # Only warn about missing terminfo entries if we explicitly asked for
183 # Only warn about missing terminfo entries if we explicitly asked for
183 # terminfo mode and we're in a formatted terminal.
184 # terminfo mode and we're in a formatted terminal.
184 if mode == "terminfo" and formatted:
185 if mode == "terminfo" and formatted:
185 ui.warn(_("no terminfo entry for setab/setaf: reverting to "
186 ui.warn(_("no terminfo entry for setab/setaf: reverting to "
186 "ECMA-48 color\n"))
187 "ECMA-48 color\n"))
187 ui._terminfoparams.clear()
188 ui._terminfoparams.clear()
188
189
189 def setup(ui):
190 def setup(ui):
190 """configure color on a ui
191 """configure color on a ui
191
192
192 That function both set the colormode for the ui object and read
193 That function both set the colormode for the ui object and read
193 the configuration looking for custom colors and effect definitions."""
194 the configuration looking for custom colors and effect definitions."""
194 mode = _modesetup(ui)
195 mode = _modesetup(ui)
195 ui._colormode = mode
196 ui._colormode = mode
196 if mode and mode != 'debug':
197 if mode and mode != 'debug':
197 configstyles(ui)
198 configstyles(ui)
198
199
199 def _modesetup(ui):
200 def _modesetup(ui):
200 if ui.plain('color'):
201 if ui.plain('color'):
201 return None
202 return None
202 config = ui.config('ui', 'color')
203 config = ui.config('ui', 'color')
203 if config == 'debug':
204 if config == 'debug':
204 return 'debug'
205 return 'debug'
205
206
206 auto = (config == 'auto')
207 auto = (config == 'auto')
207 always = False
208 always = False
208 if not auto and stringutil.parsebool(config):
209 if not auto and stringutil.parsebool(config):
209 # We want the config to behave like a boolean, "on" is actually auto,
210 # We want the config to behave like a boolean, "on" is actually auto,
210 # but "always" value is treated as a special case to reduce confusion.
211 # but "always" value is treated as a special case to reduce confusion.
211 if ui.configsource('ui', 'color') == '--color' or config == 'always':
212 if ui.configsource('ui', 'color') == '--color' or config == 'always':
212 always = True
213 always = True
213 else:
214 else:
214 auto = True
215 auto = True
215
216
216 if not always and not auto:
217 if not always and not auto:
217 return None
218 return None
218
219
219 formatted = (always or (encoding.environ.get('TERM') != 'dumb'
220 formatted = (always or (encoding.environ.get('TERM') != 'dumb'
220 and ui.formatted()))
221 and ui.formatted()))
221
222
222 mode = ui.config('color', 'mode')
223 mode = ui.config('color', 'mode')
223
224
224 # If pager is active, color.pagermode overrides color.mode.
225 # If pager is active, color.pagermode overrides color.mode.
225 if getattr(ui, 'pageractive', False):
226 if getattr(ui, 'pageractive', False):
226 mode = ui.config('color', 'pagermode', mode)
227 mode = ui.config('color', 'pagermode', mode)
227
228
228 realmode = mode
229 realmode = mode
229 if pycompat.iswindows:
230 if pycompat.iswindows:
230 from . import win32
231 from . import win32
231
232
232 term = encoding.environ.get('TERM')
233 term = encoding.environ.get('TERM')
233 # TERM won't be defined in a vanilla cmd.exe environment.
234 # TERM won't be defined in a vanilla cmd.exe environment.
234
235
235 # UNIX-like environments on Windows such as Cygwin and MSYS will
236 # UNIX-like environments on Windows such as Cygwin and MSYS will
236 # set TERM. They appear to make a best effort attempt at setting it
237 # set TERM. They appear to make a best effort attempt at setting it
237 # to something appropriate. However, not all environments with TERM
238 # to something appropriate. However, not all environments with TERM
238 # defined support ANSI.
239 # defined support ANSI.
239 ansienviron = term and 'xterm' in term
240 ansienviron = term and 'xterm' in term
240
241
241 if mode == 'auto':
242 if mode == 'auto':
242 # Since "ansi" could result in terminal gibberish, we error on the
243 # Since "ansi" could result in terminal gibberish, we error on the
243 # side of selecting "win32". However, if w32effects is not defined,
244 # side of selecting "win32". However, if w32effects is not defined,
244 # we almost certainly don't support "win32", so don't even try.
245 # we almost certainly don't support "win32", so don't even try.
245 # w32ffects is not populated when stdout is redirected, so checking
246 # w32ffects is not populated when stdout is redirected, so checking
246 # it first avoids win32 calls in a state known to error out.
247 # it first avoids win32 calls in a state known to error out.
247 if ansienviron or not w32effects or win32.enablevtmode():
248 if ansienviron or not w32effects or win32.enablevtmode():
248 realmode = 'ansi'
249 realmode = 'ansi'
249 else:
250 else:
250 realmode = 'win32'
251 realmode = 'win32'
251 # An empty w32effects is a clue that stdout is redirected, and thus
252 # An empty w32effects is a clue that stdout is redirected, and thus
252 # cannot enable VT mode.
253 # cannot enable VT mode.
253 elif mode == 'ansi' and w32effects and not ansienviron:
254 elif mode == 'ansi' and w32effects and not ansienviron:
254 win32.enablevtmode()
255 win32.enablevtmode()
255 elif mode == 'auto':
256 elif mode == 'auto':
256 realmode = 'ansi'
257 realmode = 'ansi'
257
258
258 def modewarn():
259 def modewarn():
259 # only warn if color.mode was explicitly set and we're in
260 # only warn if color.mode was explicitly set and we're in
260 # a formatted terminal
261 # a formatted terminal
261 if mode == realmode and formatted:
262 if mode == realmode and formatted:
262 ui.warn(_('warning: failed to set color mode to %s\n') % mode)
263 ui.warn(_('warning: failed to set color mode to %s\n') % mode)
263
264
264 if realmode == 'win32':
265 if realmode == 'win32':
265 ui._terminfoparams.clear()
266 ui._terminfoparams.clear()
266 if not w32effects:
267 if not w32effects:
267 modewarn()
268 modewarn()
268 return None
269 return None
269 elif realmode == 'ansi':
270 elif realmode == 'ansi':
270 ui._terminfoparams.clear()
271 ui._terminfoparams.clear()
271 elif realmode == 'terminfo':
272 elif realmode == 'terminfo':
272 _terminfosetup(ui, mode, formatted)
273 _terminfosetup(ui, mode, formatted)
273 if not ui._terminfoparams:
274 if not ui._terminfoparams:
274 ## FIXME Shouldn't we return None in this case too?
275 ## FIXME Shouldn't we return None in this case too?
275 modewarn()
276 modewarn()
276 realmode = 'ansi'
277 realmode = 'ansi'
277 else:
278 else:
278 return None
279 return None
279
280
280 if always or (auto and formatted):
281 if always or (auto and formatted):
281 return realmode
282 return realmode
282 return None
283 return None
283
284
284 def configstyles(ui):
285 def configstyles(ui):
285 ui._styles.update(_defaultstyles)
286 ui._styles.update(_defaultstyles)
286 for status, cfgeffects in ui.configitems('color'):
287 for status, cfgeffects in ui.configitems('color'):
287 if '.' not in status or status.startswith(('color.', 'terminfo.')):
288 if '.' not in status or status.startswith(('color.', 'terminfo.')):
288 continue
289 continue
289 cfgeffects = ui.configlist('color', status)
290 cfgeffects = ui.configlist('color', status)
290 if cfgeffects:
291 if cfgeffects:
291 good = []
292 good = []
292 for e in cfgeffects:
293 for e in cfgeffects:
293 if valideffect(ui, e):
294 if valideffect(ui, e):
294 good.append(e)
295 good.append(e)
295 else:
296 else:
296 ui.warn(_("ignoring unknown color/effect %r "
297 ui.warn(_("ignoring unknown color/effect %r "
297 "(configured in color.%s)\n")
298 "(configured in color.%s)\n")
298 % (e, status))
299 % (e, status))
299 ui._styles[status] = ' '.join(good)
300 ui._styles[status] = ' '.join(good)
300
301
301 def _activeeffects(ui):
302 def _activeeffects(ui):
302 '''Return the effects map for the color mode set on the ui.'''
303 '''Return the effects map for the color mode set on the ui.'''
303 if ui._colormode == 'win32':
304 if ui._colormode == 'win32':
304 return w32effects
305 return w32effects
305 elif ui._colormode is not None:
306 elif ui._colormode is not None:
306 return _effects
307 return _effects
307 return {}
308 return {}
308
309
309 def valideffect(ui, effect):
310 def valideffect(ui, effect):
310 'Determine if the effect is valid or not.'
311 'Determine if the effect is valid or not.'
311 return ((not ui._terminfoparams and effect in _activeeffects(ui))
312 return ((not ui._terminfoparams and effect in _activeeffects(ui))
312 or (effect in ui._terminfoparams
313 or (effect in ui._terminfoparams
313 or effect[:-11] in ui._terminfoparams))
314 or effect[:-11] in ui._terminfoparams))
314
315
315 def _effect_str(ui, effect):
316 def _effect_str(ui, effect):
316 '''Helper function for render_effects().'''
317 '''Helper function for render_effects().'''
317
318
318 bg = False
319 bg = False
319 if effect.endswith('_background'):
320 if effect.endswith('_background'):
320 bg = True
321 bg = True
321 effect = effect[:-11]
322 effect = effect[:-11]
322 try:
323 try:
323 attr, val, termcode = ui._terminfoparams[effect]
324 attr, val, termcode = ui._terminfoparams[effect]
324 except KeyError:
325 except KeyError:
325 return ''
326 return ''
326 if attr:
327 if attr:
327 if termcode:
328 if termcode:
328 return termcode
329 return termcode
329 else:
330 else:
330 return curses.tigetstr(pycompat.sysstr(val))
331 return curses.tigetstr(pycompat.sysstr(val))
331 elif bg:
332 elif bg:
332 return curses.tparm(curses.tigetstr(r'setab'), val)
333 return curses.tparm(curses.tigetstr(r'setab'), val)
333 else:
334 else:
334 return curses.tparm(curses.tigetstr(r'setaf'), val)
335 return curses.tparm(curses.tigetstr(r'setaf'), val)
335
336
336 def _mergeeffects(text, start, stop):
337 def _mergeeffects(text, start, stop):
337 """Insert start sequence at every occurrence of stop sequence
338 """Insert start sequence at every occurrence of stop sequence
338
339
339 >>> s = _mergeeffects(b'cyan', b'[C]', b'|')
340 >>> s = _mergeeffects(b'cyan', b'[C]', b'|')
340 >>> s = _mergeeffects(s + b'yellow', b'[Y]', b'|')
341 >>> s = _mergeeffects(s + b'yellow', b'[Y]', b'|')
341 >>> s = _mergeeffects(b'ma' + s + b'genta', b'[M]', b'|')
342 >>> s = _mergeeffects(b'ma' + s + b'genta', b'[M]', b'|')
342 >>> s = _mergeeffects(b'red' + s, b'[R]', b'|')
343 >>> s = _mergeeffects(b'red' + s, b'[R]', b'|')
343 >>> s
344 >>> s
344 '[R]red[M]ma[Y][C]cyan|[R][M][Y]yellow|[R][M]genta|'
345 '[R]red[M]ma[Y][C]cyan|[R][M][Y]yellow|[R][M]genta|'
345 """
346 """
346 parts = []
347 parts = []
347 for t in text.split(stop):
348 for t in text.split(stop):
348 if not t:
349 if not t:
349 continue
350 continue
350 parts.extend([start, t, stop])
351 parts.extend([start, t, stop])
351 return ''.join(parts)
352 return ''.join(parts)
352
353
353 def _render_effects(ui, text, effects):
354 def _render_effects(ui, text, effects):
354 'Wrap text in commands to turn on each effect.'
355 'Wrap text in commands to turn on each effect.'
355 if not text:
356 if not text:
356 return text
357 return text
357 if ui._terminfoparams:
358 if ui._terminfoparams:
358 start = ''.join(_effect_str(ui, effect)
359 start = ''.join(_effect_str(ui, effect)
359 for effect in ['none'] + effects.split())
360 for effect in ['none'] + effects.split())
360 stop = _effect_str(ui, 'none')
361 stop = _effect_str(ui, 'none')
361 else:
362 else:
362 activeeffects = _activeeffects(ui)
363 activeeffects = _activeeffects(ui)
363 start = [pycompat.bytestr(activeeffects[e])
364 start = [pycompat.bytestr(activeeffects[e])
364 for e in ['none'] + effects.split()]
365 for e in ['none'] + effects.split()]
365 start = '\033[' + ';'.join(start) + 'm'
366 start = '\033[' + ';'.join(start) + 'm'
366 stop = '\033[' + pycompat.bytestr(activeeffects['none']) + 'm'
367 stop = '\033[' + pycompat.bytestr(activeeffects['none']) + 'm'
367 return _mergeeffects(text, start, stop)
368 return _mergeeffects(text, start, stop)
368
369
369 _ansieffectre = re.compile(br'\x1b\[[0-9;]*m')
370 _ansieffectre = re.compile(br'\x1b\[[0-9;]*m')
370
371
371 def stripeffects(text):
372 def stripeffects(text):
372 """Strip ANSI control codes which could be inserted by colorlabel()"""
373 """Strip ANSI control codes which could be inserted by colorlabel()"""
373 return _ansieffectre.sub('', text)
374 return _ansieffectre.sub('', text)
374
375
375 def colorlabel(ui, msg, label):
376 def colorlabel(ui, msg, label):
376 """add color control code according to the mode"""
377 """add color control code according to the mode"""
377 if ui._colormode == 'debug':
378 if ui._colormode == 'debug':
378 if label and msg:
379 if label and msg:
379 if msg.endswith('\n'):
380 if msg.endswith('\n'):
380 msg = "[%s|%s]\n" % (label, msg[:-1])
381 msg = "[%s|%s]\n" % (label, msg[:-1])
381 else:
382 else:
382 msg = "[%s|%s]" % (label, msg)
383 msg = "[%s|%s]" % (label, msg)
383 elif ui._colormode is not None:
384 elif ui._colormode is not None:
384 effects = []
385 effects = []
385 for l in label.split():
386 for l in label.split():
386 s = ui._styles.get(l, '')
387 s = ui._styles.get(l, '')
387 if s:
388 if s:
388 effects.append(s)
389 effects.append(s)
389 elif valideffect(ui, l):
390 elif valideffect(ui, l):
390 effects.append(l)
391 effects.append(l)
391 effects = ' '.join(effects)
392 effects = ' '.join(effects)
392 if effects:
393 if effects:
393 msg = '\n'.join([_render_effects(ui, line, effects)
394 msg = '\n'.join([_render_effects(ui, line, effects)
394 for line in msg.split('\n')])
395 for line in msg.split('\n')])
395 return msg
396 return msg
396
397
397 w32effects = None
398 w32effects = None
398 if pycompat.iswindows:
399 if pycompat.iswindows:
399 import ctypes
400 import ctypes
400
401
401 _kernel32 = ctypes.windll.kernel32
402 _kernel32 = ctypes.windll.kernel32
402
403
403 _WORD = ctypes.c_ushort
404 _WORD = ctypes.c_ushort
404
405
405 _INVALID_HANDLE_VALUE = -1
406 _INVALID_HANDLE_VALUE = -1
406
407
407 class _COORD(ctypes.Structure):
408 class _COORD(ctypes.Structure):
408 _fields_ = [('X', ctypes.c_short),
409 _fields_ = [('X', ctypes.c_short),
409 ('Y', ctypes.c_short)]
410 ('Y', ctypes.c_short)]
410
411
411 class _SMALL_RECT(ctypes.Structure):
412 class _SMALL_RECT(ctypes.Structure):
412 _fields_ = [('Left', ctypes.c_short),
413 _fields_ = [('Left', ctypes.c_short),
413 ('Top', ctypes.c_short),
414 ('Top', ctypes.c_short),
414 ('Right', ctypes.c_short),
415 ('Right', ctypes.c_short),
415 ('Bottom', ctypes.c_short)]
416 ('Bottom', ctypes.c_short)]
416
417
417 class _CONSOLE_SCREEN_BUFFER_INFO(ctypes.Structure):
418 class _CONSOLE_SCREEN_BUFFER_INFO(ctypes.Structure):
418 _fields_ = [('dwSize', _COORD),
419 _fields_ = [('dwSize', _COORD),
419 ('dwCursorPosition', _COORD),
420 ('dwCursorPosition', _COORD),
420 ('wAttributes', _WORD),
421 ('wAttributes', _WORD),
421 ('srWindow', _SMALL_RECT),
422 ('srWindow', _SMALL_RECT),
422 ('dwMaximumWindowSize', _COORD)]
423 ('dwMaximumWindowSize', _COORD)]
423
424
424 _STD_OUTPUT_HANDLE = 0xfffffff5 # (DWORD)-11
425 _STD_OUTPUT_HANDLE = 0xfffffff5 # (DWORD)-11
425 _STD_ERROR_HANDLE = 0xfffffff4 # (DWORD)-12
426 _STD_ERROR_HANDLE = 0xfffffff4 # (DWORD)-12
426
427
427 _FOREGROUND_BLUE = 0x0001
428 _FOREGROUND_BLUE = 0x0001
428 _FOREGROUND_GREEN = 0x0002
429 _FOREGROUND_GREEN = 0x0002
429 _FOREGROUND_RED = 0x0004
430 _FOREGROUND_RED = 0x0004
430 _FOREGROUND_INTENSITY = 0x0008
431 _FOREGROUND_INTENSITY = 0x0008
431
432
432 _BACKGROUND_BLUE = 0x0010
433 _BACKGROUND_BLUE = 0x0010
433 _BACKGROUND_GREEN = 0x0020
434 _BACKGROUND_GREEN = 0x0020
434 _BACKGROUND_RED = 0x0040
435 _BACKGROUND_RED = 0x0040
435 _BACKGROUND_INTENSITY = 0x0080
436 _BACKGROUND_INTENSITY = 0x0080
436
437
437 _COMMON_LVB_REVERSE_VIDEO = 0x4000
438 _COMMON_LVB_REVERSE_VIDEO = 0x4000
438 _COMMON_LVB_UNDERSCORE = 0x8000
439 _COMMON_LVB_UNDERSCORE = 0x8000
439
440
440 # http://msdn.microsoft.com/en-us/library/ms682088%28VS.85%29.aspx
441 # http://msdn.microsoft.com/en-us/library/ms682088%28VS.85%29.aspx
441 w32effects = {
442 w32effects = {
442 'none': -1,
443 'none': -1,
443 'black': 0,
444 'black': 0,
444 'red': _FOREGROUND_RED,
445 'red': _FOREGROUND_RED,
445 'green': _FOREGROUND_GREEN,
446 'green': _FOREGROUND_GREEN,
446 'yellow': _FOREGROUND_RED | _FOREGROUND_GREEN,
447 'yellow': _FOREGROUND_RED | _FOREGROUND_GREEN,
447 'blue': _FOREGROUND_BLUE,
448 'blue': _FOREGROUND_BLUE,
448 'magenta': _FOREGROUND_BLUE | _FOREGROUND_RED,
449 'magenta': _FOREGROUND_BLUE | _FOREGROUND_RED,
449 'cyan': _FOREGROUND_BLUE | _FOREGROUND_GREEN,
450 'cyan': _FOREGROUND_BLUE | _FOREGROUND_GREEN,
450 'white': _FOREGROUND_RED | _FOREGROUND_GREEN | _FOREGROUND_BLUE,
451 'white': _FOREGROUND_RED | _FOREGROUND_GREEN | _FOREGROUND_BLUE,
451 'bold': _FOREGROUND_INTENSITY,
452 'bold': _FOREGROUND_INTENSITY,
452 'black_background': 0x100, # unused value > 0x0f
453 'black_background': 0x100, # unused value > 0x0f
453 'red_background': _BACKGROUND_RED,
454 'red_background': _BACKGROUND_RED,
454 'green_background': _BACKGROUND_GREEN,
455 'green_background': _BACKGROUND_GREEN,
455 'yellow_background': _BACKGROUND_RED | _BACKGROUND_GREEN,
456 'yellow_background': _BACKGROUND_RED | _BACKGROUND_GREEN,
456 'blue_background': _BACKGROUND_BLUE,
457 'blue_background': _BACKGROUND_BLUE,
457 'purple_background': _BACKGROUND_BLUE | _BACKGROUND_RED,
458 'purple_background': _BACKGROUND_BLUE | _BACKGROUND_RED,
458 'cyan_background': _BACKGROUND_BLUE | _BACKGROUND_GREEN,
459 'cyan_background': _BACKGROUND_BLUE | _BACKGROUND_GREEN,
459 'white_background': (_BACKGROUND_RED | _BACKGROUND_GREEN |
460 'white_background': (_BACKGROUND_RED | _BACKGROUND_GREEN |
460 _BACKGROUND_BLUE),
461 _BACKGROUND_BLUE),
461 'bold_background': _BACKGROUND_INTENSITY,
462 'bold_background': _BACKGROUND_INTENSITY,
462 'underline': _COMMON_LVB_UNDERSCORE, # double-byte charsets only
463 'underline': _COMMON_LVB_UNDERSCORE, # double-byte charsets only
463 'inverse': _COMMON_LVB_REVERSE_VIDEO, # double-byte charsets only
464 'inverse': _COMMON_LVB_REVERSE_VIDEO, # double-byte charsets only
464 }
465 }
465
466
466 passthrough = {_FOREGROUND_INTENSITY,
467 passthrough = {_FOREGROUND_INTENSITY,
467 _BACKGROUND_INTENSITY,
468 _BACKGROUND_INTENSITY,
468 _COMMON_LVB_UNDERSCORE,
469 _COMMON_LVB_UNDERSCORE,
469 _COMMON_LVB_REVERSE_VIDEO}
470 _COMMON_LVB_REVERSE_VIDEO}
470
471
471 stdout = _kernel32.GetStdHandle(
472 stdout = _kernel32.GetStdHandle(
472 _STD_OUTPUT_HANDLE) # don't close the handle returned
473 _STD_OUTPUT_HANDLE) # don't close the handle returned
473 if stdout is None or stdout == _INVALID_HANDLE_VALUE:
474 if stdout is None or stdout == _INVALID_HANDLE_VALUE:
474 w32effects = None
475 w32effects = None
475 else:
476 else:
476 csbi = _CONSOLE_SCREEN_BUFFER_INFO()
477 csbi = _CONSOLE_SCREEN_BUFFER_INFO()
477 if not _kernel32.GetConsoleScreenBufferInfo(
478 if not _kernel32.GetConsoleScreenBufferInfo(
478 stdout, ctypes.byref(csbi)):
479 stdout, ctypes.byref(csbi)):
479 # stdout may not support GetConsoleScreenBufferInfo()
480 # stdout may not support GetConsoleScreenBufferInfo()
480 # when called from subprocess or redirected
481 # when called from subprocess or redirected
481 w32effects = None
482 w32effects = None
482 else:
483 else:
483 origattr = csbi.wAttributes
484 origattr = csbi.wAttributes
484 ansire = re.compile('\033\[([^m]*)m([^\033]*)(.*)',
485 ansire = re.compile('\033\[([^m]*)m([^\033]*)(.*)',
485 re.MULTILINE | re.DOTALL)
486 re.MULTILINE | re.DOTALL)
486
487
487 def win32print(ui, writefunc, *msgs, **opts):
488 def win32print(ui, writefunc, *msgs, **opts):
488 for text in msgs:
489 for text in msgs:
489 _win32print(ui, text, writefunc, **opts)
490 _win32print(ui, text, writefunc, **opts)
490
491
491 def _win32print(ui, text, writefunc, **opts):
492 def _win32print(ui, text, writefunc, **opts):
492 label = opts.get(r'label', '')
493 label = opts.get(r'label', '')
493 attr = origattr
494 attr = origattr
494
495
495 def mapcolor(val, attr):
496 def mapcolor(val, attr):
496 if val == -1:
497 if val == -1:
497 return origattr
498 return origattr
498 elif val in passthrough:
499 elif val in passthrough:
499 return attr | val
500 return attr | val
500 elif val > 0x0f:
501 elif val > 0x0f:
501 return (val & 0x70) | (attr & 0x8f)
502 return (val & 0x70) | (attr & 0x8f)
502 else:
503 else:
503 return (val & 0x07) | (attr & 0xf8)
504 return (val & 0x07) | (attr & 0xf8)
504
505
505 # determine console attributes based on labels
506 # determine console attributes based on labels
506 for l in label.split():
507 for l in label.split():
507 style = ui._styles.get(l, '')
508 style = ui._styles.get(l, '')
508 for effect in style.split():
509 for effect in style.split():
509 try:
510 try:
510 attr = mapcolor(w32effects[effect], attr)
511 attr = mapcolor(w32effects[effect], attr)
511 except KeyError:
512 except KeyError:
512 # w32effects could not have certain attributes so we skip
513 # w32effects could not have certain attributes so we skip
513 # them if not found
514 # them if not found
514 pass
515 pass
515 # hack to ensure regexp finds data
516 # hack to ensure regexp finds data
516 if not text.startswith('\033['):
517 if not text.startswith('\033['):
517 text = '\033[m' + text
518 text = '\033[m' + text
518
519
519 # Look for ANSI-like codes embedded in text
520 # Look for ANSI-like codes embedded in text
520 m = re.match(ansire, text)
521 m = re.match(ansire, text)
521
522
522 try:
523 try:
523 while m:
524 while m:
524 for sattr in m.group(1).split(';'):
525 for sattr in m.group(1).split(';'):
525 if sattr:
526 if sattr:
526 attr = mapcolor(int(sattr), attr)
527 attr = mapcolor(int(sattr), attr)
527 ui.flush()
528 ui.flush()
528 _kernel32.SetConsoleTextAttribute(stdout, attr)
529 _kernel32.SetConsoleTextAttribute(stdout, attr)
529 writefunc(m.group(2), **opts)
530 writefunc(m.group(2), **opts)
530 m = re.match(ansire, m.group(3))
531 m = re.match(ansire, m.group(3))
531 finally:
532 finally:
532 # Explicitly reset original attributes
533 # Explicitly reset original attributes
533 ui.flush()
534 ui.flush()
534 _kernel32.SetConsoleTextAttribute(stdout, origattr)
535 _kernel32.SetConsoleTextAttribute(stdout, origattr)
@@ -1,537 +1,537 b''
1 # commandserver.py - communicate with Mercurial's API over a pipe
1 # commandserver.py - communicate with Mercurial's API over a pipe
2 #
2 #
3 # Copyright Matt Mackall <mpm@selenic.com>
3 # Copyright Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import errno
10 import errno
11 import gc
11 import gc
12 import os
12 import os
13 import random
13 import random
14 import signal
14 import signal
15 import socket
15 import socket
16 import struct
16 import struct
17 import traceback
17 import traceback
18
18
19 try:
19 try:
20 import selectors
20 import selectors
21 selectors.BaseSelector
21 selectors.BaseSelector
22 except ImportError:
22 except ImportError:
23 from .thirdparty import selectors2 as selectors
23 from .thirdparty import selectors2 as selectors
24
24
25 from .i18n import _
25 from .i18n import _
26 from . import (
26 from . import (
27 encoding,
27 encoding,
28 error,
28 error,
29 pycompat,
29 pycompat,
30 util,
30 util,
31 )
31 )
32 from .utils import (
32 from .utils import (
33 procutil,
33 procutil,
34 )
34 )
35
35
36 logfile = None
36 logfile = None
37
37
38 def log(*args):
38 def log(*args):
39 if not logfile:
39 if not logfile:
40 return
40 return
41
41
42 for a in args:
42 for a in args:
43 logfile.write(str(a))
43 logfile.write(str(a))
44
44
45 logfile.flush()
45 logfile.flush()
46
46
47 class channeledoutput(object):
47 class channeledoutput(object):
48 """
48 """
49 Write data to out in the following format:
49 Write data to out in the following format:
50
50
51 data length (unsigned int),
51 data length (unsigned int),
52 data
52 data
53 """
53 """
54 def __init__(self, out, channel):
54 def __init__(self, out, channel):
55 self.out = out
55 self.out = out
56 self.channel = channel
56 self.channel = channel
57
57
58 @property
58 @property
59 def name(self):
59 def name(self):
60 return '<%c-channel>' % self.channel
60 return '<%c-channel>' % self.channel
61
61
62 def write(self, data):
62 def write(self, data):
63 if not data:
63 if not data:
64 return
64 return
65 # single write() to guarantee the same atomicity as the underlying file
65 # single write() to guarantee the same atomicity as the underlying file
66 self.out.write(struct.pack('>cI', self.channel, len(data)) + data)
66 self.out.write(struct.pack('>cI', self.channel, len(data)) + data)
67 self.out.flush()
67 self.out.flush()
68
68
69 def __getattr__(self, attr):
69 def __getattr__(self, attr):
70 if attr in ('isatty', 'fileno', 'tell', 'seek'):
70 if attr in ('isatty', 'fileno', 'tell', 'seek'):
71 raise AttributeError(attr)
71 raise AttributeError(attr)
72 return getattr(self.out, attr)
72 return getattr(self.out, attr)
73
73
74 class channeledinput(object):
74 class channeledinput(object):
75 """
75 """
76 Read data from in_.
76 Read data from in_.
77
77
78 Requests for input are written to out in the following format:
78 Requests for input are written to out in the following format:
79 channel identifier - 'I' for plain input, 'L' line based (1 byte)
79 channel identifier - 'I' for plain input, 'L' line based (1 byte)
80 how many bytes to send at most (unsigned int),
80 how many bytes to send at most (unsigned int),
81
81
82 The client replies with:
82 The client replies with:
83 data length (unsigned int), 0 meaning EOF
83 data length (unsigned int), 0 meaning EOF
84 data
84 data
85 """
85 """
86
86
87 maxchunksize = 4 * 1024
87 maxchunksize = 4 * 1024
88
88
89 def __init__(self, in_, out, channel):
89 def __init__(self, in_, out, channel):
90 self.in_ = in_
90 self.in_ = in_
91 self.out = out
91 self.out = out
92 self.channel = channel
92 self.channel = channel
93
93
94 @property
94 @property
95 def name(self):
95 def name(self):
96 return '<%c-channel>' % self.channel
96 return '<%c-channel>' % self.channel
97
97
98 def read(self, size=-1):
98 def read(self, size=-1):
99 if size < 0:
99 if size < 0:
100 # if we need to consume all the clients input, ask for 4k chunks
100 # if we need to consume all the clients input, ask for 4k chunks
101 # so the pipe doesn't fill up risking a deadlock
101 # so the pipe doesn't fill up risking a deadlock
102 size = self.maxchunksize
102 size = self.maxchunksize
103 s = self._read(size, self.channel)
103 s = self._read(size, self.channel)
104 buf = s
104 buf = s
105 while s:
105 while s:
106 s = self._read(size, self.channel)
106 s = self._read(size, self.channel)
107 buf += s
107 buf += s
108
108
109 return buf
109 return buf
110 else:
110 else:
111 return self._read(size, self.channel)
111 return self._read(size, self.channel)
112
112
113 def _read(self, size, channel):
113 def _read(self, size, channel):
114 if not size:
114 if not size:
115 return ''
115 return ''
116 assert size > 0
116 assert size > 0
117
117
118 # tell the client we need at most size bytes
118 # tell the client we need at most size bytes
119 self.out.write(struct.pack('>cI', channel, size))
119 self.out.write(struct.pack('>cI', channel, size))
120 self.out.flush()
120 self.out.flush()
121
121
122 length = self.in_.read(4)
122 length = self.in_.read(4)
123 length = struct.unpack('>I', length)[0]
123 length = struct.unpack('>I', length)[0]
124 if not length:
124 if not length:
125 return ''
125 return ''
126 else:
126 else:
127 return self.in_.read(length)
127 return self.in_.read(length)
128
128
129 def readline(self, size=-1):
129 def readline(self, size=-1):
130 if size < 0:
130 if size < 0:
131 size = self.maxchunksize
131 size = self.maxchunksize
132 s = self._read(size, 'L')
132 s = self._read(size, 'L')
133 buf = s
133 buf = s
134 # keep asking for more until there's either no more or
134 # keep asking for more until there's either no more or
135 # we got a full line
135 # we got a full line
136 while s and s[-1] != '\n':
136 while s and s[-1] != '\n':
137 s = self._read(size, 'L')
137 s = self._read(size, 'L')
138 buf += s
138 buf += s
139
139
140 return buf
140 return buf
141 else:
141 else:
142 return self._read(size, 'L')
142 return self._read(size, 'L')
143
143
144 def __iter__(self):
144 def __iter__(self):
145 return self
145 return self
146
146
147 def next(self):
147 def next(self):
148 l = self.readline()
148 l = self.readline()
149 if not l:
149 if not l:
150 raise StopIteration
150 raise StopIteration
151 return l
151 return l
152
152
153 def __getattr__(self, attr):
153 def __getattr__(self, attr):
154 if attr in ('isatty', 'fileno', 'tell', 'seek'):
154 if attr in ('isatty', 'fileno', 'tell', 'seek'):
155 raise AttributeError(attr)
155 raise AttributeError(attr)
156 return getattr(self.in_, attr)
156 return getattr(self.in_, attr)
157
157
158 class server(object):
158 class server(object):
159 """
159 """
160 Listens for commands on fin, runs them and writes the output on a channel
160 Listens for commands on fin, runs them and writes the output on a channel
161 based stream to fout.
161 based stream to fout.
162 """
162 """
163 def __init__(self, ui, repo, fin, fout):
163 def __init__(self, ui, repo, fin, fout):
164 self.cwd = pycompat.getcwd()
164 self.cwd = pycompat.getcwd()
165
165
166 # developer config: cmdserver.log
166 # developer config: cmdserver.log
167 logpath = ui.config("cmdserver", "log")
167 logpath = ui.config("cmdserver", "log")
168 if logpath:
168 if logpath:
169 global logfile
169 global logfile
170 if logpath == '-':
170 if logpath == '-':
171 # write log on a special 'd' (debug) channel
171 # write log on a special 'd' (debug) channel
172 logfile = channeledoutput(fout, 'd')
172 logfile = channeledoutput(fout, 'd')
173 else:
173 else:
174 logfile = open(logpath, 'a')
174 logfile = open(logpath, 'a')
175
175
176 if repo:
176 if repo:
177 # the ui here is really the repo ui so take its baseui so we don't
177 # the ui here is really the repo ui so take its baseui so we don't
178 # end up with its local configuration
178 # end up with its local configuration
179 self.ui = repo.baseui
179 self.ui = repo.baseui
180 self.repo = repo
180 self.repo = repo
181 self.repoui = repo.ui
181 self.repoui = repo.ui
182 else:
182 else:
183 self.ui = ui
183 self.ui = ui
184 self.repo = self.repoui = None
184 self.repo = self.repoui = None
185
185
186 self.cerr = channeledoutput(fout, 'e')
186 self.cerr = channeledoutput(fout, 'e')
187 self.cout = channeledoutput(fout, 'o')
187 self.cout = channeledoutput(fout, 'o')
188 self.cin = channeledinput(fin, fout, 'I')
188 self.cin = channeledinput(fin, fout, 'I')
189 self.cresult = channeledoutput(fout, 'r')
189 self.cresult = channeledoutput(fout, 'r')
190
190
191 self.client = fin
191 self.client = fin
192
192
193 def cleanup(self):
193 def cleanup(self):
194 """release and restore resources taken during server session"""
194 """release and restore resources taken during server session"""
195
195
196 def _read(self, size):
196 def _read(self, size):
197 if not size:
197 if not size:
198 return ''
198 return ''
199
199
200 data = self.client.read(size)
200 data = self.client.read(size)
201
201
202 # is the other end closed?
202 # is the other end closed?
203 if not data:
203 if not data:
204 raise EOFError
204 raise EOFError
205
205
206 return data
206 return data
207
207
208 def _readstr(self):
208 def _readstr(self):
209 """read a string from the channel
209 """read a string from the channel
210
210
211 format:
211 format:
212 data length (uint32), data
212 data length (uint32), data
213 """
213 """
214 length = struct.unpack('>I', self._read(4))[0]
214 length = struct.unpack('>I', self._read(4))[0]
215 if not length:
215 if not length:
216 return ''
216 return ''
217 return self._read(length)
217 return self._read(length)
218
218
219 def _readlist(self):
219 def _readlist(self):
220 """read a list of NULL separated strings from the channel"""
220 """read a list of NULL separated strings from the channel"""
221 s = self._readstr()
221 s = self._readstr()
222 if s:
222 if s:
223 return s.split('\0')
223 return s.split('\0')
224 else:
224 else:
225 return []
225 return []
226
226
227 def runcommand(self):
227 def runcommand(self):
228 """ reads a list of \0 terminated arguments, executes
228 """ reads a list of \0 terminated arguments, executes
229 and writes the return code to the result channel """
229 and writes the return code to the result channel """
230 from . import dispatch # avoid cycle
230 from . import dispatch # avoid cycle
231
231
232 args = self._readlist()
232 args = self._readlist()
233
233
234 # copy the uis so changes (e.g. --config or --verbose) don't
234 # copy the uis so changes (e.g. --config or --verbose) don't
235 # persist between requests
235 # persist between requests
236 copiedui = self.ui.copy()
236 copiedui = self.ui.copy()
237 uis = [copiedui]
237 uis = [copiedui]
238 if self.repo:
238 if self.repo:
239 self.repo.baseui = copiedui
239 self.repo.baseui = copiedui
240 # clone ui without using ui.copy because this is protected
240 # clone ui without using ui.copy because this is protected
241 repoui = self.repoui.__class__(self.repoui)
241 repoui = self.repoui.__class__(self.repoui)
242 repoui.copy = copiedui.copy # redo copy protection
242 repoui.copy = copiedui.copy # redo copy protection
243 uis.append(repoui)
243 uis.append(repoui)
244 self.repo.ui = self.repo.dirstate._ui = repoui
244 self.repo.ui = self.repo.dirstate._ui = repoui
245 self.repo.invalidateall()
245 self.repo.invalidateall()
246
246
247 for ui in uis:
247 for ui in uis:
248 ui.resetstate()
248 ui.resetstate()
249 # any kind of interaction must use server channels, but chg may
249 # any kind of interaction must use server channels, but chg may
250 # replace channels by fully functional tty files. so nontty is
250 # replace channels by fully functional tty files. so nontty is
251 # enforced only if cin is a channel.
251 # enforced only if cin is a channel.
252 if not util.safehasattr(self.cin, 'fileno'):
252 if not util.safehasattr(self.cin, 'fileno'):
253 ui.setconfig('ui', 'nontty', 'true', 'commandserver')
253 ui.setconfig('ui', 'nontty', 'true', 'commandserver')
254
254
255 req = dispatch.request(args[:], copiedui, self.repo, self.cin,
255 req = dispatch.request(args[:], copiedui, self.repo, self.cin,
256 self.cout, self.cerr)
256 self.cout, self.cerr)
257
257
258 try:
258 try:
259 ret = dispatch.dispatch(req) & 255
259 ret = dispatch.dispatch(req) & 255
260 self.cresult.write(struct.pack('>i', int(ret)))
260 self.cresult.write(struct.pack('>i', int(ret)))
261 finally:
261 finally:
262 # restore old cwd
262 # restore old cwd
263 if '--cwd' in args:
263 if '--cwd' in args:
264 os.chdir(self.cwd)
264 os.chdir(self.cwd)
265
265
266 def getencoding(self):
266 def getencoding(self):
267 """ writes the current encoding to the result channel """
267 """ writes the current encoding to the result channel """
268 self.cresult.write(encoding.encoding)
268 self.cresult.write(encoding.encoding)
269
269
270 def serveone(self):
270 def serveone(self):
271 cmd = self.client.readline()[:-1]
271 cmd = self.client.readline()[:-1]
272 if cmd:
272 if cmd:
273 handler = self.capabilities.get(cmd)
273 handler = self.capabilities.get(cmd)
274 if handler:
274 if handler:
275 handler(self)
275 handler(self)
276 else:
276 else:
277 # clients are expected to check what commands are supported by
277 # clients are expected to check what commands are supported by
278 # looking at the servers capabilities
278 # looking at the servers capabilities
279 raise error.Abort(_('unknown command %s') % cmd)
279 raise error.Abort(_('unknown command %s') % cmd)
280
280
281 return cmd != ''
281 return cmd != ''
282
282
283 capabilities = {'runcommand': runcommand,
283 capabilities = {'runcommand': runcommand,
284 'getencoding': getencoding}
284 'getencoding': getencoding}
285
285
286 def serve(self):
286 def serve(self):
287 hellomsg = 'capabilities: ' + ' '.join(sorted(self.capabilities))
287 hellomsg = 'capabilities: ' + ' '.join(sorted(self.capabilities))
288 hellomsg += '\n'
288 hellomsg += '\n'
289 hellomsg += 'encoding: ' + encoding.encoding
289 hellomsg += 'encoding: ' + encoding.encoding
290 hellomsg += '\n'
290 hellomsg += '\n'
291 hellomsg += 'pid: %d' % procutil.getpid()
291 hellomsg += 'pid: %d' % procutil.getpid()
292 if util.safehasattr(os, 'getpgid'):
292 if util.safehasattr(os, 'getpgid'):
293 hellomsg += '\n'
293 hellomsg += '\n'
294 hellomsg += 'pgid: %d' % os.getpgid(0)
294 hellomsg += 'pgid: %d' % os.getpgid(0)
295
295
296 # write the hello msg in -one- chunk
296 # write the hello msg in -one- chunk
297 self.cout.write(hellomsg)
297 self.cout.write(hellomsg)
298
298
299 try:
299 try:
300 while self.serveone():
300 while self.serveone():
301 pass
301 pass
302 except EOFError:
302 except EOFError:
303 # we'll get here if the client disconnected while we were reading
303 # we'll get here if the client disconnected while we were reading
304 # its request
304 # its request
305 return 1
305 return 1
306
306
307 return 0
307 return 0
308
308
309 class pipeservice(object):
309 class pipeservice(object):
310 def __init__(self, ui, repo, opts):
310 def __init__(self, ui, repo, opts):
311 self.ui = ui
311 self.ui = ui
312 self.repo = repo
312 self.repo = repo
313
313
314 def init(self):
314 def init(self):
315 pass
315 pass
316
316
317 def run(self):
317 def run(self):
318 ui = self.ui
318 ui = self.ui
319 # redirect stdio to null device so that broken extensions or in-process
319 # redirect stdio to null device so that broken extensions or in-process
320 # hooks will never cause corruption of channel protocol.
320 # hooks will never cause corruption of channel protocol.
321 with procutil.protectedstdio(ui.fin, ui.fout) as (fin, fout):
321 with procutil.protectedstdio(ui.fin, ui.fout) as (fin, fout):
322 try:
322 try:
323 sv = server(ui, self.repo, fin, fout)
323 sv = server(ui, self.repo, fin, fout)
324 return sv.serve()
324 return sv.serve()
325 finally:
325 finally:
326 sv.cleanup()
326 sv.cleanup()
327
327
328 def _initworkerprocess():
328 def _initworkerprocess():
329 # use a different process group from the master process, in order to:
329 # use a different process group from the master process, in order to:
330 # 1. make the current process group no longer "orphaned" (because the
330 # 1. make the current process group no longer "orphaned" (because the
331 # parent of this process is in a different process group while
331 # parent of this process is in a different process group while
332 # remains in a same session)
332 # remains in a same session)
333 # according to POSIX 2.2.2.52, orphaned process group will ignore
333 # according to POSIX 2.2.2.52, orphaned process group will ignore
334 # terminal-generated stop signals like SIGTSTP (Ctrl+Z), which will
334 # terminal-generated stop signals like SIGTSTP (Ctrl+Z), which will
335 # cause trouble for things like ncurses.
335 # cause trouble for things like ncurses.
336 # 2. the client can use kill(-pgid, sig) to simulate terminal-generated
336 # 2. the client can use kill(-pgid, sig) to simulate terminal-generated
337 # SIGINT (Ctrl+C) and process-exit-generated SIGHUP. our child
337 # SIGINT (Ctrl+C) and process-exit-generated SIGHUP. our child
338 # processes like ssh will be killed properly, without affecting
338 # processes like ssh will be killed properly, without affecting
339 # unrelated processes.
339 # unrelated processes.
340 os.setpgid(0, 0)
340 os.setpgid(0, 0)
341 # change random state otherwise forked request handlers would have a
341 # change random state otherwise forked request handlers would have a
342 # same state inherited from parent.
342 # same state inherited from parent.
343 random.seed()
343 random.seed()
344
344
345 def _serverequest(ui, repo, conn, createcmdserver):
345 def _serverequest(ui, repo, conn, createcmdserver):
346 fin = conn.makefile('rb')
346 fin = conn.makefile('rb')
347 fout = conn.makefile('wb')
347 fout = conn.makefile('wb')
348 sv = None
348 sv = None
349 try:
349 try:
350 sv = createcmdserver(repo, conn, fin, fout)
350 sv = createcmdserver(repo, conn, fin, fout)
351 try:
351 try:
352 sv.serve()
352 sv.serve()
353 # handle exceptions that may be raised by command server. most of
353 # handle exceptions that may be raised by command server. most of
354 # known exceptions are caught by dispatch.
354 # known exceptions are caught by dispatch.
355 except error.Abort as inst:
355 except error.Abort as inst:
356 ui.warn(_('abort: %s\n') % inst)
356 ui.error(_('abort: %s\n') % inst)
357 except IOError as inst:
357 except IOError as inst:
358 if inst.errno != errno.EPIPE:
358 if inst.errno != errno.EPIPE:
359 raise
359 raise
360 except KeyboardInterrupt:
360 except KeyboardInterrupt:
361 pass
361 pass
362 finally:
362 finally:
363 sv.cleanup()
363 sv.cleanup()
364 except: # re-raises
364 except: # re-raises
365 # also write traceback to error channel. otherwise client cannot
365 # also write traceback to error channel. otherwise client cannot
366 # see it because it is written to server's stderr by default.
366 # see it because it is written to server's stderr by default.
367 if sv:
367 if sv:
368 cerr = sv.cerr
368 cerr = sv.cerr
369 else:
369 else:
370 cerr = channeledoutput(fout, 'e')
370 cerr = channeledoutput(fout, 'e')
371 traceback.print_exc(file=cerr)
371 traceback.print_exc(file=cerr)
372 raise
372 raise
373 finally:
373 finally:
374 fin.close()
374 fin.close()
375 try:
375 try:
376 fout.close() # implicit flush() may cause another EPIPE
376 fout.close() # implicit flush() may cause another EPIPE
377 except IOError as inst:
377 except IOError as inst:
378 if inst.errno != errno.EPIPE:
378 if inst.errno != errno.EPIPE:
379 raise
379 raise
380
380
381 class unixservicehandler(object):
381 class unixservicehandler(object):
382 """Set of pluggable operations for unix-mode services
382 """Set of pluggable operations for unix-mode services
383
383
384 Almost all methods except for createcmdserver() are called in the main
384 Almost all methods except for createcmdserver() are called in the main
385 process. You can't pass mutable resource back from createcmdserver().
385 process. You can't pass mutable resource back from createcmdserver().
386 """
386 """
387
387
388 pollinterval = None
388 pollinterval = None
389
389
390 def __init__(self, ui):
390 def __init__(self, ui):
391 self.ui = ui
391 self.ui = ui
392
392
393 def bindsocket(self, sock, address):
393 def bindsocket(self, sock, address):
394 util.bindunixsocket(sock, address)
394 util.bindunixsocket(sock, address)
395 sock.listen(socket.SOMAXCONN)
395 sock.listen(socket.SOMAXCONN)
396 self.ui.status(_('listening at %s\n') % address)
396 self.ui.status(_('listening at %s\n') % address)
397 self.ui.flush() # avoid buffering of status message
397 self.ui.flush() # avoid buffering of status message
398
398
399 def unlinksocket(self, address):
399 def unlinksocket(self, address):
400 os.unlink(address)
400 os.unlink(address)
401
401
402 def shouldexit(self):
402 def shouldexit(self):
403 """True if server should shut down; checked per pollinterval"""
403 """True if server should shut down; checked per pollinterval"""
404 return False
404 return False
405
405
406 def newconnection(self):
406 def newconnection(self):
407 """Called when main process notices new connection"""
407 """Called when main process notices new connection"""
408
408
409 def createcmdserver(self, repo, conn, fin, fout):
409 def createcmdserver(self, repo, conn, fin, fout):
410 """Create new command server instance; called in the process that
410 """Create new command server instance; called in the process that
411 serves for the current connection"""
411 serves for the current connection"""
412 return server(self.ui, repo, fin, fout)
412 return server(self.ui, repo, fin, fout)
413
413
414 class unixforkingservice(object):
414 class unixforkingservice(object):
415 """
415 """
416 Listens on unix domain socket and forks server per connection
416 Listens on unix domain socket and forks server per connection
417 """
417 """
418
418
419 def __init__(self, ui, repo, opts, handler=None):
419 def __init__(self, ui, repo, opts, handler=None):
420 self.ui = ui
420 self.ui = ui
421 self.repo = repo
421 self.repo = repo
422 self.address = opts['address']
422 self.address = opts['address']
423 if not util.safehasattr(socket, 'AF_UNIX'):
423 if not util.safehasattr(socket, 'AF_UNIX'):
424 raise error.Abort(_('unsupported platform'))
424 raise error.Abort(_('unsupported platform'))
425 if not self.address:
425 if not self.address:
426 raise error.Abort(_('no socket path specified with --address'))
426 raise error.Abort(_('no socket path specified with --address'))
427 self._servicehandler = handler or unixservicehandler(ui)
427 self._servicehandler = handler or unixservicehandler(ui)
428 self._sock = None
428 self._sock = None
429 self._oldsigchldhandler = None
429 self._oldsigchldhandler = None
430 self._workerpids = set() # updated by signal handler; do not iterate
430 self._workerpids = set() # updated by signal handler; do not iterate
431 self._socketunlinked = None
431 self._socketunlinked = None
432
432
433 def init(self):
433 def init(self):
434 self._sock = socket.socket(socket.AF_UNIX)
434 self._sock = socket.socket(socket.AF_UNIX)
435 self._servicehandler.bindsocket(self._sock, self.address)
435 self._servicehandler.bindsocket(self._sock, self.address)
436 if util.safehasattr(procutil, 'unblocksignal'):
436 if util.safehasattr(procutil, 'unblocksignal'):
437 procutil.unblocksignal(signal.SIGCHLD)
437 procutil.unblocksignal(signal.SIGCHLD)
438 o = signal.signal(signal.SIGCHLD, self._sigchldhandler)
438 o = signal.signal(signal.SIGCHLD, self._sigchldhandler)
439 self._oldsigchldhandler = o
439 self._oldsigchldhandler = o
440 self._socketunlinked = False
440 self._socketunlinked = False
441
441
442 def _unlinksocket(self):
442 def _unlinksocket(self):
443 if not self._socketunlinked:
443 if not self._socketunlinked:
444 self._servicehandler.unlinksocket(self.address)
444 self._servicehandler.unlinksocket(self.address)
445 self._socketunlinked = True
445 self._socketunlinked = True
446
446
447 def _cleanup(self):
447 def _cleanup(self):
448 signal.signal(signal.SIGCHLD, self._oldsigchldhandler)
448 signal.signal(signal.SIGCHLD, self._oldsigchldhandler)
449 self._sock.close()
449 self._sock.close()
450 self._unlinksocket()
450 self._unlinksocket()
451 # don't kill child processes as they have active clients, just wait
451 # don't kill child processes as they have active clients, just wait
452 self._reapworkers(0)
452 self._reapworkers(0)
453
453
454 def run(self):
454 def run(self):
455 try:
455 try:
456 self._mainloop()
456 self._mainloop()
457 finally:
457 finally:
458 self._cleanup()
458 self._cleanup()
459
459
460 def _mainloop(self):
460 def _mainloop(self):
461 exiting = False
461 exiting = False
462 h = self._servicehandler
462 h = self._servicehandler
463 selector = selectors.DefaultSelector()
463 selector = selectors.DefaultSelector()
464 selector.register(self._sock, selectors.EVENT_READ)
464 selector.register(self._sock, selectors.EVENT_READ)
465 while True:
465 while True:
466 if not exiting and h.shouldexit():
466 if not exiting and h.shouldexit():
467 # clients can no longer connect() to the domain socket, so
467 # clients can no longer connect() to the domain socket, so
468 # we stop queuing new requests.
468 # we stop queuing new requests.
469 # for requests that are queued (connect()-ed, but haven't been
469 # for requests that are queued (connect()-ed, but haven't been
470 # accept()-ed), handle them before exit. otherwise, clients
470 # accept()-ed), handle them before exit. otherwise, clients
471 # waiting for recv() will receive ECONNRESET.
471 # waiting for recv() will receive ECONNRESET.
472 self._unlinksocket()
472 self._unlinksocket()
473 exiting = True
473 exiting = True
474 ready = selector.select(timeout=h.pollinterval)
474 ready = selector.select(timeout=h.pollinterval)
475 if not ready:
475 if not ready:
476 # only exit if we completed all queued requests
476 # only exit if we completed all queued requests
477 if exiting:
477 if exiting:
478 break
478 break
479 continue
479 continue
480 try:
480 try:
481 conn, _addr = self._sock.accept()
481 conn, _addr = self._sock.accept()
482 except socket.error as inst:
482 except socket.error as inst:
483 if inst.args[0] == errno.EINTR:
483 if inst.args[0] == errno.EINTR:
484 continue
484 continue
485 raise
485 raise
486
486
487 pid = os.fork()
487 pid = os.fork()
488 if pid:
488 if pid:
489 try:
489 try:
490 self.ui.debug('forked worker process (pid=%d)\n' % pid)
490 self.ui.debug('forked worker process (pid=%d)\n' % pid)
491 self._workerpids.add(pid)
491 self._workerpids.add(pid)
492 h.newconnection()
492 h.newconnection()
493 finally:
493 finally:
494 conn.close() # release handle in parent process
494 conn.close() # release handle in parent process
495 else:
495 else:
496 try:
496 try:
497 selector.close()
497 selector.close()
498 self._sock.close()
498 self._sock.close()
499 self._runworker(conn)
499 self._runworker(conn)
500 conn.close()
500 conn.close()
501 os._exit(0)
501 os._exit(0)
502 except: # never return, hence no re-raises
502 except: # never return, hence no re-raises
503 try:
503 try:
504 self.ui.traceback(force=True)
504 self.ui.traceback(force=True)
505 finally:
505 finally:
506 os._exit(255)
506 os._exit(255)
507 selector.close()
507 selector.close()
508
508
509 def _sigchldhandler(self, signal, frame):
509 def _sigchldhandler(self, signal, frame):
510 self._reapworkers(os.WNOHANG)
510 self._reapworkers(os.WNOHANG)
511
511
512 def _reapworkers(self, options):
512 def _reapworkers(self, options):
513 while self._workerpids:
513 while self._workerpids:
514 try:
514 try:
515 pid, _status = os.waitpid(-1, options)
515 pid, _status = os.waitpid(-1, options)
516 except OSError as inst:
516 except OSError as inst:
517 if inst.errno == errno.EINTR:
517 if inst.errno == errno.EINTR:
518 continue
518 continue
519 if inst.errno != errno.ECHILD:
519 if inst.errno != errno.ECHILD:
520 raise
520 raise
521 # no child processes at all (reaped by other waitpid()?)
521 # no child processes at all (reaped by other waitpid()?)
522 self._workerpids.clear()
522 self._workerpids.clear()
523 return
523 return
524 if pid == 0:
524 if pid == 0:
525 # no waitable child processes
525 # no waitable child processes
526 return
526 return
527 self.ui.debug('worker process exited (pid=%d)\n' % pid)
527 self.ui.debug('worker process exited (pid=%d)\n' % pid)
528 self._workerpids.discard(pid)
528 self._workerpids.discard(pid)
529
529
530 def _runworker(self, conn):
530 def _runworker(self, conn):
531 signal.signal(signal.SIGCHLD, self._oldsigchldhandler)
531 signal.signal(signal.SIGCHLD, self._oldsigchldhandler)
532 _initworkerprocess()
532 _initworkerprocess()
533 h = self._servicehandler
533 h = self._servicehandler
534 try:
534 try:
535 _serverequest(self.ui, self.repo, conn, h.createcmdserver)
535 _serverequest(self.ui, self.repo, conn, h.createcmdserver)
536 finally:
536 finally:
537 gc.collect() # trigger __del__ since worker process uses os._exit
537 gc.collect() # trigger __del__ since worker process uses os._exit
@@ -1,1066 +1,1066 b''
1 # dispatch.py - command dispatching for mercurial
1 # dispatch.py - command dispatching for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import, print_function
8 from __future__ import absolute_import, print_function
9
9
10 import difflib
10 import difflib
11 import errno
11 import errno
12 import getopt
12 import getopt
13 import os
13 import os
14 import pdb
14 import pdb
15 import re
15 import re
16 import signal
16 import signal
17 import sys
17 import sys
18 import time
18 import time
19 import traceback
19 import traceback
20
20
21
21
22 from .i18n import _
22 from .i18n import _
23
23
24 from . import (
24 from . import (
25 cmdutil,
25 cmdutil,
26 color,
26 color,
27 commands,
27 commands,
28 demandimport,
28 demandimport,
29 encoding,
29 encoding,
30 error,
30 error,
31 extensions,
31 extensions,
32 fancyopts,
32 fancyopts,
33 help,
33 help,
34 hg,
34 hg,
35 hook,
35 hook,
36 profiling,
36 profiling,
37 pycompat,
37 pycompat,
38 scmutil,
38 scmutil,
39 ui as uimod,
39 ui as uimod,
40 util,
40 util,
41 )
41 )
42
42
43 from .utils import (
43 from .utils import (
44 procutil,
44 procutil,
45 stringutil,
45 stringutil,
46 )
46 )
47
47
48 class request(object):
48 class request(object):
49 def __init__(self, args, ui=None, repo=None, fin=None, fout=None,
49 def __init__(self, args, ui=None, repo=None, fin=None, fout=None,
50 ferr=None, prereposetups=None):
50 ferr=None, prereposetups=None):
51 self.args = args
51 self.args = args
52 self.ui = ui
52 self.ui = ui
53 self.repo = repo
53 self.repo = repo
54
54
55 # input/output/error streams
55 # input/output/error streams
56 self.fin = fin
56 self.fin = fin
57 self.fout = fout
57 self.fout = fout
58 self.ferr = ferr
58 self.ferr = ferr
59
59
60 # remember options pre-parsed by _earlyparseopts()
60 # remember options pre-parsed by _earlyparseopts()
61 self.earlyoptions = {}
61 self.earlyoptions = {}
62
62
63 # reposetups which run before extensions, useful for chg to pre-fill
63 # reposetups which run before extensions, useful for chg to pre-fill
64 # low-level repo state (for example, changelog) before extensions.
64 # low-level repo state (for example, changelog) before extensions.
65 self.prereposetups = prereposetups or []
65 self.prereposetups = prereposetups or []
66
66
67 def _runexithandlers(self):
67 def _runexithandlers(self):
68 exc = None
68 exc = None
69 handlers = self.ui._exithandlers
69 handlers = self.ui._exithandlers
70 try:
70 try:
71 while handlers:
71 while handlers:
72 func, args, kwargs = handlers.pop()
72 func, args, kwargs = handlers.pop()
73 try:
73 try:
74 func(*args, **kwargs)
74 func(*args, **kwargs)
75 except: # re-raises below
75 except: # re-raises below
76 if exc is None:
76 if exc is None:
77 exc = sys.exc_info()[1]
77 exc = sys.exc_info()[1]
78 self.ui.warn(('error in exit handlers:\n'))
78 self.ui.warn(('error in exit handlers:\n'))
79 self.ui.traceback(force=True)
79 self.ui.traceback(force=True)
80 finally:
80 finally:
81 if exc is not None:
81 if exc is not None:
82 raise exc
82 raise exc
83
83
84 def run():
84 def run():
85 "run the command in sys.argv"
85 "run the command in sys.argv"
86 initstdio()
86 initstdio()
87 req = request(pycompat.sysargv[1:])
87 req = request(pycompat.sysargv[1:])
88 err = None
88 err = None
89 try:
89 try:
90 status = dispatch(req)
90 status = dispatch(req)
91 except error.StdioError as e:
91 except error.StdioError as e:
92 err = e
92 err = e
93 status = -1
93 status = -1
94
94
95 # In all cases we try to flush stdio streams.
95 # In all cases we try to flush stdio streams.
96 if util.safehasattr(req.ui, 'fout'):
96 if util.safehasattr(req.ui, 'fout'):
97 try:
97 try:
98 req.ui.fout.flush()
98 req.ui.fout.flush()
99 except IOError as e:
99 except IOError as e:
100 err = e
100 err = e
101 status = -1
101 status = -1
102
102
103 if util.safehasattr(req.ui, 'ferr'):
103 if util.safehasattr(req.ui, 'ferr'):
104 try:
104 try:
105 if err is not None and err.errno != errno.EPIPE:
105 if err is not None and err.errno != errno.EPIPE:
106 req.ui.ferr.write('abort: %s\n' %
106 req.ui.ferr.write('abort: %s\n' %
107 encoding.strtolocal(err.strerror))
107 encoding.strtolocal(err.strerror))
108 req.ui.ferr.flush()
108 req.ui.ferr.flush()
109 # There's not much we can do about an I/O error here. So (possibly)
109 # There's not much we can do about an I/O error here. So (possibly)
110 # change the status code and move on.
110 # change the status code and move on.
111 except IOError:
111 except IOError:
112 status = -1
112 status = -1
113
113
114 _silencestdio()
114 _silencestdio()
115 sys.exit(status & 255)
115 sys.exit(status & 255)
116
116
117 if pycompat.ispy3:
117 if pycompat.ispy3:
118 def initstdio():
118 def initstdio():
119 pass
119 pass
120
120
121 def _silencestdio():
121 def _silencestdio():
122 for fp in (sys.stdout, sys.stderr):
122 for fp in (sys.stdout, sys.stderr):
123 # Check if the file is okay
123 # Check if the file is okay
124 try:
124 try:
125 fp.flush()
125 fp.flush()
126 continue
126 continue
127 except IOError:
127 except IOError:
128 pass
128 pass
129 # Otherwise mark it as closed to silence "Exception ignored in"
129 # Otherwise mark it as closed to silence "Exception ignored in"
130 # message emitted by the interpreter finalizer. Be careful to
130 # message emitted by the interpreter finalizer. Be careful to
131 # not close procutil.stdout, which may be a fdopen-ed file object
131 # not close procutil.stdout, which may be a fdopen-ed file object
132 # and its close() actually closes the underlying file descriptor.
132 # and its close() actually closes the underlying file descriptor.
133 try:
133 try:
134 fp.close()
134 fp.close()
135 except IOError:
135 except IOError:
136 pass
136 pass
137 else:
137 else:
138 def initstdio():
138 def initstdio():
139 for fp in (sys.stdin, sys.stdout, sys.stderr):
139 for fp in (sys.stdin, sys.stdout, sys.stderr):
140 procutil.setbinary(fp)
140 procutil.setbinary(fp)
141
141
142 def _silencestdio():
142 def _silencestdio():
143 pass
143 pass
144
144
145 def _getsimilar(symbols, value):
145 def _getsimilar(symbols, value):
146 sim = lambda x: difflib.SequenceMatcher(None, value, x).ratio()
146 sim = lambda x: difflib.SequenceMatcher(None, value, x).ratio()
147 # The cutoff for similarity here is pretty arbitrary. It should
147 # The cutoff for similarity here is pretty arbitrary. It should
148 # probably be investigated and tweaked.
148 # probably be investigated and tweaked.
149 return [s for s in symbols if sim(s) > 0.6]
149 return [s for s in symbols if sim(s) > 0.6]
150
150
151 def _reportsimilar(write, similar):
151 def _reportsimilar(write, similar):
152 if len(similar) == 1:
152 if len(similar) == 1:
153 write(_("(did you mean %s?)\n") % similar[0])
153 write(_("(did you mean %s?)\n") % similar[0])
154 elif similar:
154 elif similar:
155 ss = ", ".join(sorted(similar))
155 ss = ", ".join(sorted(similar))
156 write(_("(did you mean one of %s?)\n") % ss)
156 write(_("(did you mean one of %s?)\n") % ss)
157
157
158 def _formatparse(write, inst):
158 def _formatparse(write, inst):
159 similar = []
159 similar = []
160 if isinstance(inst, error.UnknownIdentifier):
160 if isinstance(inst, error.UnknownIdentifier):
161 # make sure to check fileset first, as revset can invoke fileset
161 # make sure to check fileset first, as revset can invoke fileset
162 similar = _getsimilar(inst.symbols, inst.function)
162 similar = _getsimilar(inst.symbols, inst.function)
163 if len(inst.args) > 1:
163 if len(inst.args) > 1:
164 write(_("hg: parse error at %s: %s\n") %
164 write(_("hg: parse error at %s: %s\n") %
165 (pycompat.bytestr(inst.args[1]), inst.args[0]))
165 (pycompat.bytestr(inst.args[1]), inst.args[0]))
166 if inst.args[0].startswith(' '):
166 if inst.args[0].startswith(' '):
167 write(_("unexpected leading whitespace\n"))
167 write(_("unexpected leading whitespace\n"))
168 else:
168 else:
169 write(_("hg: parse error: %s\n") % inst.args[0])
169 write(_("hg: parse error: %s\n") % inst.args[0])
170 _reportsimilar(write, similar)
170 _reportsimilar(write, similar)
171 if inst.hint:
171 if inst.hint:
172 write(_("(%s)\n") % inst.hint)
172 write(_("(%s)\n") % inst.hint)
173
173
174 def _formatargs(args):
174 def _formatargs(args):
175 return ' '.join(procutil.shellquote(a) for a in args)
175 return ' '.join(procutil.shellquote(a) for a in args)
176
176
177 def dispatch(req):
177 def dispatch(req):
178 """run the command specified in req.args; returns an integer status code"""
178 """run the command specified in req.args; returns an integer status code"""
179 if req.ferr:
179 if req.ferr:
180 ferr = req.ferr
180 ferr = req.ferr
181 elif req.ui:
181 elif req.ui:
182 ferr = req.ui.ferr
182 ferr = req.ui.ferr
183 else:
183 else:
184 ferr = procutil.stderr
184 ferr = procutil.stderr
185
185
186 try:
186 try:
187 if not req.ui:
187 if not req.ui:
188 req.ui = uimod.ui.load()
188 req.ui = uimod.ui.load()
189 req.earlyoptions.update(_earlyparseopts(req.ui, req.args))
189 req.earlyoptions.update(_earlyparseopts(req.ui, req.args))
190 if req.earlyoptions['traceback']:
190 if req.earlyoptions['traceback']:
191 req.ui.setconfig('ui', 'traceback', 'on', '--traceback')
191 req.ui.setconfig('ui', 'traceback', 'on', '--traceback')
192
192
193 # set ui streams from the request
193 # set ui streams from the request
194 if req.fin:
194 if req.fin:
195 req.ui.fin = req.fin
195 req.ui.fin = req.fin
196 if req.fout:
196 if req.fout:
197 req.ui.fout = req.fout
197 req.ui.fout = req.fout
198 if req.ferr:
198 if req.ferr:
199 req.ui.ferr = req.ferr
199 req.ui.ferr = req.ferr
200 except error.Abort as inst:
200 except error.Abort as inst:
201 ferr.write(_("abort: %s\n") % inst)
201 ferr.write(_("abort: %s\n") % inst)
202 if inst.hint:
202 if inst.hint:
203 ferr.write(_("(%s)\n") % inst.hint)
203 ferr.write(_("(%s)\n") % inst.hint)
204 return -1
204 return -1
205 except error.ParseError as inst:
205 except error.ParseError as inst:
206 _formatparse(ferr.write, inst)
206 _formatparse(ferr.write, inst)
207 return -1
207 return -1
208
208
209 msg = _formatargs(req.args)
209 msg = _formatargs(req.args)
210 starttime = util.timer()
210 starttime = util.timer()
211 ret = 1 # default of Python exit code on unhandled exception
211 ret = 1 # default of Python exit code on unhandled exception
212 try:
212 try:
213 ret = _runcatch(req) or 0
213 ret = _runcatch(req) or 0
214 except error.ProgrammingError as inst:
214 except error.ProgrammingError as inst:
215 req.ui.warn(_('** ProgrammingError: %s\n') % inst)
215 req.ui.error(_('** ProgrammingError: %s\n') % inst)
216 if inst.hint:
216 if inst.hint:
217 req.ui.warn(_('** (%s)\n') % inst.hint)
217 req.ui.error(_('** (%s)\n') % inst.hint)
218 raise
218 raise
219 except KeyboardInterrupt as inst:
219 except KeyboardInterrupt as inst:
220 try:
220 try:
221 if isinstance(inst, error.SignalInterrupt):
221 if isinstance(inst, error.SignalInterrupt):
222 msg = _("killed!\n")
222 msg = _("killed!\n")
223 else:
223 else:
224 msg = _("interrupted!\n")
224 msg = _("interrupted!\n")
225 req.ui.warn(msg)
225 req.ui.error(msg)
226 except error.SignalInterrupt:
226 except error.SignalInterrupt:
227 # maybe pager would quit without consuming all the output, and
227 # maybe pager would quit without consuming all the output, and
228 # SIGPIPE was raised. we cannot print anything in this case.
228 # SIGPIPE was raised. we cannot print anything in this case.
229 pass
229 pass
230 except IOError as inst:
230 except IOError as inst:
231 if inst.errno != errno.EPIPE:
231 if inst.errno != errno.EPIPE:
232 raise
232 raise
233 ret = -1
233 ret = -1
234 finally:
234 finally:
235 duration = util.timer() - starttime
235 duration = util.timer() - starttime
236 req.ui.flush()
236 req.ui.flush()
237 if req.ui.logblockedtimes:
237 if req.ui.logblockedtimes:
238 req.ui._blockedtimes['command_duration'] = duration * 1000
238 req.ui._blockedtimes['command_duration'] = duration * 1000
239 req.ui.log('uiblocked', 'ui blocked ms',
239 req.ui.log('uiblocked', 'ui blocked ms',
240 **pycompat.strkwargs(req.ui._blockedtimes))
240 **pycompat.strkwargs(req.ui._blockedtimes))
241 req.ui.log("commandfinish", "%s exited %d after %0.2f seconds\n",
241 req.ui.log("commandfinish", "%s exited %d after %0.2f seconds\n",
242 msg, ret & 255, duration)
242 msg, ret & 255, duration)
243 try:
243 try:
244 req._runexithandlers()
244 req._runexithandlers()
245 except: # exiting, so no re-raises
245 except: # exiting, so no re-raises
246 ret = ret or -1
246 ret = ret or -1
247 return ret
247 return ret
248
248
249 def _runcatch(req):
249 def _runcatch(req):
250 def catchterm(*args):
250 def catchterm(*args):
251 raise error.SignalInterrupt
251 raise error.SignalInterrupt
252
252
253 ui = req.ui
253 ui = req.ui
254 try:
254 try:
255 for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM':
255 for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM':
256 num = getattr(signal, name, None)
256 num = getattr(signal, name, None)
257 if num:
257 if num:
258 signal.signal(num, catchterm)
258 signal.signal(num, catchterm)
259 except ValueError:
259 except ValueError:
260 pass # happens if called in a thread
260 pass # happens if called in a thread
261
261
262 def _runcatchfunc():
262 def _runcatchfunc():
263 realcmd = None
263 realcmd = None
264 try:
264 try:
265 cmdargs = fancyopts.fancyopts(req.args[:], commands.globalopts, {})
265 cmdargs = fancyopts.fancyopts(req.args[:], commands.globalopts, {})
266 cmd = cmdargs[0]
266 cmd = cmdargs[0]
267 aliases, entry = cmdutil.findcmd(cmd, commands.table, False)
267 aliases, entry = cmdutil.findcmd(cmd, commands.table, False)
268 realcmd = aliases[0]
268 realcmd = aliases[0]
269 except (error.UnknownCommand, error.AmbiguousCommand,
269 except (error.UnknownCommand, error.AmbiguousCommand,
270 IndexError, getopt.GetoptError):
270 IndexError, getopt.GetoptError):
271 # Don't handle this here. We know the command is
271 # Don't handle this here. We know the command is
272 # invalid, but all we're worried about for now is that
272 # invalid, but all we're worried about for now is that
273 # it's not a command that server operators expect to
273 # it's not a command that server operators expect to
274 # be safe to offer to users in a sandbox.
274 # be safe to offer to users in a sandbox.
275 pass
275 pass
276 if realcmd == 'serve' and '--stdio' in cmdargs:
276 if realcmd == 'serve' and '--stdio' in cmdargs:
277 # We want to constrain 'hg serve --stdio' instances pretty
277 # We want to constrain 'hg serve --stdio' instances pretty
278 # closely, as many shared-ssh access tools want to grant
278 # closely, as many shared-ssh access tools want to grant
279 # access to run *only* 'hg -R $repo serve --stdio'. We
279 # access to run *only* 'hg -R $repo serve --stdio'. We
280 # restrict to exactly that set of arguments, and prohibit
280 # restrict to exactly that set of arguments, and prohibit
281 # any repo name that starts with '--' to prevent
281 # any repo name that starts with '--' to prevent
282 # shenanigans wherein a user does something like pass
282 # shenanigans wherein a user does something like pass
283 # --debugger or --config=ui.debugger=1 as a repo
283 # --debugger or --config=ui.debugger=1 as a repo
284 # name. This used to actually run the debugger.
284 # name. This used to actually run the debugger.
285 if (len(req.args) != 4 or
285 if (len(req.args) != 4 or
286 req.args[0] != '-R' or
286 req.args[0] != '-R' or
287 req.args[1].startswith('--') or
287 req.args[1].startswith('--') or
288 req.args[2] != 'serve' or
288 req.args[2] != 'serve' or
289 req.args[3] != '--stdio'):
289 req.args[3] != '--stdio'):
290 raise error.Abort(
290 raise error.Abort(
291 _('potentially unsafe serve --stdio invocation: %s') %
291 _('potentially unsafe serve --stdio invocation: %s') %
292 (stringutil.pprint(req.args),))
292 (stringutil.pprint(req.args),))
293
293
294 try:
294 try:
295 debugger = 'pdb'
295 debugger = 'pdb'
296 debugtrace = {
296 debugtrace = {
297 'pdb': pdb.set_trace
297 'pdb': pdb.set_trace
298 }
298 }
299 debugmortem = {
299 debugmortem = {
300 'pdb': pdb.post_mortem
300 'pdb': pdb.post_mortem
301 }
301 }
302
302
303 # read --config before doing anything else
303 # read --config before doing anything else
304 # (e.g. to change trust settings for reading .hg/hgrc)
304 # (e.g. to change trust settings for reading .hg/hgrc)
305 cfgs = _parseconfig(req.ui, req.earlyoptions['config'])
305 cfgs = _parseconfig(req.ui, req.earlyoptions['config'])
306
306
307 if req.repo:
307 if req.repo:
308 # copy configs that were passed on the cmdline (--config) to
308 # copy configs that were passed on the cmdline (--config) to
309 # the repo ui
309 # the repo ui
310 for sec, name, val in cfgs:
310 for sec, name, val in cfgs:
311 req.repo.ui.setconfig(sec, name, val, source='--config')
311 req.repo.ui.setconfig(sec, name, val, source='--config')
312
312
313 # developer config: ui.debugger
313 # developer config: ui.debugger
314 debugger = ui.config("ui", "debugger")
314 debugger = ui.config("ui", "debugger")
315 debugmod = pdb
315 debugmod = pdb
316 if not debugger or ui.plain():
316 if not debugger or ui.plain():
317 # if we are in HGPLAIN mode, then disable custom debugging
317 # if we are in HGPLAIN mode, then disable custom debugging
318 debugger = 'pdb'
318 debugger = 'pdb'
319 elif req.earlyoptions['debugger']:
319 elif req.earlyoptions['debugger']:
320 # This import can be slow for fancy debuggers, so only
320 # This import can be slow for fancy debuggers, so only
321 # do it when absolutely necessary, i.e. when actual
321 # do it when absolutely necessary, i.e. when actual
322 # debugging has been requested
322 # debugging has been requested
323 with demandimport.deactivated():
323 with demandimport.deactivated():
324 try:
324 try:
325 debugmod = __import__(debugger)
325 debugmod = __import__(debugger)
326 except ImportError:
326 except ImportError:
327 pass # Leave debugmod = pdb
327 pass # Leave debugmod = pdb
328
328
329 debugtrace[debugger] = debugmod.set_trace
329 debugtrace[debugger] = debugmod.set_trace
330 debugmortem[debugger] = debugmod.post_mortem
330 debugmortem[debugger] = debugmod.post_mortem
331
331
332 # enter the debugger before command execution
332 # enter the debugger before command execution
333 if req.earlyoptions['debugger']:
333 if req.earlyoptions['debugger']:
334 ui.warn(_("entering debugger - "
334 ui.warn(_("entering debugger - "
335 "type c to continue starting hg or h for help\n"))
335 "type c to continue starting hg or h for help\n"))
336
336
337 if (debugger != 'pdb' and
337 if (debugger != 'pdb' and
338 debugtrace[debugger] == debugtrace['pdb']):
338 debugtrace[debugger] == debugtrace['pdb']):
339 ui.warn(_("%s debugger specified "
339 ui.warn(_("%s debugger specified "
340 "but its module was not found\n") % debugger)
340 "but its module was not found\n") % debugger)
341 with demandimport.deactivated():
341 with demandimport.deactivated():
342 debugtrace[debugger]()
342 debugtrace[debugger]()
343 try:
343 try:
344 return _dispatch(req)
344 return _dispatch(req)
345 finally:
345 finally:
346 ui.flush()
346 ui.flush()
347 except: # re-raises
347 except: # re-raises
348 # enter the debugger when we hit an exception
348 # enter the debugger when we hit an exception
349 if req.earlyoptions['debugger']:
349 if req.earlyoptions['debugger']:
350 traceback.print_exc()
350 traceback.print_exc()
351 debugmortem[debugger](sys.exc_info()[2])
351 debugmortem[debugger](sys.exc_info()[2])
352 raise
352 raise
353
353
354 return _callcatch(ui, _runcatchfunc)
354 return _callcatch(ui, _runcatchfunc)
355
355
356 def _callcatch(ui, func):
356 def _callcatch(ui, func):
357 """like scmutil.callcatch but handles more high-level exceptions about
357 """like scmutil.callcatch but handles more high-level exceptions about
358 config parsing and commands. besides, use handlecommandexception to handle
358 config parsing and commands. besides, use handlecommandexception to handle
359 uncaught exceptions.
359 uncaught exceptions.
360 """
360 """
361 try:
361 try:
362 return scmutil.callcatch(ui, func)
362 return scmutil.callcatch(ui, func)
363 except error.AmbiguousCommand as inst:
363 except error.AmbiguousCommand as inst:
364 ui.warn(_("hg: command '%s' is ambiguous:\n %s\n") %
364 ui.warn(_("hg: command '%s' is ambiguous:\n %s\n") %
365 (inst.args[0], " ".join(inst.args[1])))
365 (inst.args[0], " ".join(inst.args[1])))
366 except error.CommandError as inst:
366 except error.CommandError as inst:
367 if inst.args[0]:
367 if inst.args[0]:
368 ui.pager('help')
368 ui.pager('help')
369 msgbytes = pycompat.bytestr(inst.args[1])
369 msgbytes = pycompat.bytestr(inst.args[1])
370 ui.warn(_("hg %s: %s\n") % (inst.args[0], msgbytes))
370 ui.warn(_("hg %s: %s\n") % (inst.args[0], msgbytes))
371 commands.help_(ui, inst.args[0], full=False, command=True)
371 commands.help_(ui, inst.args[0], full=False, command=True)
372 else:
372 else:
373 ui.pager('help')
373 ui.pager('help')
374 ui.warn(_("hg: %s\n") % inst.args[1])
374 ui.warn(_("hg: %s\n") % inst.args[1])
375 commands.help_(ui, 'shortlist')
375 commands.help_(ui, 'shortlist')
376 except error.ParseError as inst:
376 except error.ParseError as inst:
377 _formatparse(ui.warn, inst)
377 _formatparse(ui.warn, inst)
378 return -1
378 return -1
379 except error.UnknownCommand as inst:
379 except error.UnknownCommand as inst:
380 nocmdmsg = _("hg: unknown command '%s'\n") % inst.args[0]
380 nocmdmsg = _("hg: unknown command '%s'\n") % inst.args[0]
381 try:
381 try:
382 # check if the command is in a disabled extension
382 # check if the command is in a disabled extension
383 # (but don't check for extensions themselves)
383 # (but don't check for extensions themselves)
384 formatted = help.formattedhelp(ui, commands, inst.args[0],
384 formatted = help.formattedhelp(ui, commands, inst.args[0],
385 unknowncmd=True)
385 unknowncmd=True)
386 ui.warn(nocmdmsg)
386 ui.warn(nocmdmsg)
387 ui.write(formatted)
387 ui.write(formatted)
388 except (error.UnknownCommand, error.Abort):
388 except (error.UnknownCommand, error.Abort):
389 suggested = False
389 suggested = False
390 if len(inst.args) == 2:
390 if len(inst.args) == 2:
391 sim = _getsimilar(inst.args[1], inst.args[0])
391 sim = _getsimilar(inst.args[1], inst.args[0])
392 if sim:
392 if sim:
393 ui.warn(nocmdmsg)
393 ui.warn(nocmdmsg)
394 _reportsimilar(ui.warn, sim)
394 _reportsimilar(ui.warn, sim)
395 suggested = True
395 suggested = True
396 if not suggested:
396 if not suggested:
397 ui.pager('help')
397 ui.pager('help')
398 ui.warn(nocmdmsg)
398 ui.warn(nocmdmsg)
399 commands.help_(ui, 'shortlist')
399 commands.help_(ui, 'shortlist')
400 except IOError:
400 except IOError:
401 raise
401 raise
402 except KeyboardInterrupt:
402 except KeyboardInterrupt:
403 raise
403 raise
404 except: # probably re-raises
404 except: # probably re-raises
405 if not handlecommandexception(ui):
405 if not handlecommandexception(ui):
406 raise
406 raise
407
407
408 return -1
408 return -1
409
409
410 def aliasargs(fn, givenargs):
410 def aliasargs(fn, givenargs):
411 args = []
411 args = []
412 # only care about alias 'args', ignore 'args' set by extensions.wrapfunction
412 # only care about alias 'args', ignore 'args' set by extensions.wrapfunction
413 if not util.safehasattr(fn, '_origfunc'):
413 if not util.safehasattr(fn, '_origfunc'):
414 args = getattr(fn, 'args', args)
414 args = getattr(fn, 'args', args)
415 if args:
415 if args:
416 cmd = ' '.join(map(procutil.shellquote, args))
416 cmd = ' '.join(map(procutil.shellquote, args))
417
417
418 nums = []
418 nums = []
419 def replacer(m):
419 def replacer(m):
420 num = int(m.group(1)) - 1
420 num = int(m.group(1)) - 1
421 nums.append(num)
421 nums.append(num)
422 if num < len(givenargs):
422 if num < len(givenargs):
423 return givenargs[num]
423 return givenargs[num]
424 raise error.Abort(_('too few arguments for command alias'))
424 raise error.Abort(_('too few arguments for command alias'))
425 cmd = re.sub(br'\$(\d+|\$)', replacer, cmd)
425 cmd = re.sub(br'\$(\d+|\$)', replacer, cmd)
426 givenargs = [x for i, x in enumerate(givenargs)
426 givenargs = [x for i, x in enumerate(givenargs)
427 if i not in nums]
427 if i not in nums]
428 args = pycompat.shlexsplit(cmd)
428 args = pycompat.shlexsplit(cmd)
429 return args + givenargs
429 return args + givenargs
430
430
431 def aliasinterpolate(name, args, cmd):
431 def aliasinterpolate(name, args, cmd):
432 '''interpolate args into cmd for shell aliases
432 '''interpolate args into cmd for shell aliases
433
433
434 This also handles $0, $@ and "$@".
434 This also handles $0, $@ and "$@".
435 '''
435 '''
436 # util.interpolate can't deal with "$@" (with quotes) because it's only
436 # util.interpolate can't deal with "$@" (with quotes) because it's only
437 # built to match prefix + patterns.
437 # built to match prefix + patterns.
438 replacemap = dict(('$%d' % (i + 1), arg) for i, arg in enumerate(args))
438 replacemap = dict(('$%d' % (i + 1), arg) for i, arg in enumerate(args))
439 replacemap['$0'] = name
439 replacemap['$0'] = name
440 replacemap['$$'] = '$'
440 replacemap['$$'] = '$'
441 replacemap['$@'] = ' '.join(args)
441 replacemap['$@'] = ' '.join(args)
442 # Typical Unix shells interpolate "$@" (with quotes) as all the positional
442 # Typical Unix shells interpolate "$@" (with quotes) as all the positional
443 # parameters, separated out into words. Emulate the same behavior here by
443 # parameters, separated out into words. Emulate the same behavior here by
444 # quoting the arguments individually. POSIX shells will then typically
444 # quoting the arguments individually. POSIX shells will then typically
445 # tokenize each argument into exactly one word.
445 # tokenize each argument into exactly one word.
446 replacemap['"$@"'] = ' '.join(procutil.shellquote(arg) for arg in args)
446 replacemap['"$@"'] = ' '.join(procutil.shellquote(arg) for arg in args)
447 # escape '\$' for regex
447 # escape '\$' for regex
448 regex = '|'.join(replacemap.keys()).replace('$', br'\$')
448 regex = '|'.join(replacemap.keys()).replace('$', br'\$')
449 r = re.compile(regex)
449 r = re.compile(regex)
450 return r.sub(lambda x: replacemap[x.group()], cmd)
450 return r.sub(lambda x: replacemap[x.group()], cmd)
451
451
452 class cmdalias(object):
452 class cmdalias(object):
453 def __init__(self, ui, name, definition, cmdtable, source):
453 def __init__(self, ui, name, definition, cmdtable, source):
454 self.name = self.cmd = name
454 self.name = self.cmd = name
455 self.cmdname = ''
455 self.cmdname = ''
456 self.definition = definition
456 self.definition = definition
457 self.fn = None
457 self.fn = None
458 self.givenargs = []
458 self.givenargs = []
459 self.opts = []
459 self.opts = []
460 self.help = ''
460 self.help = ''
461 self.badalias = None
461 self.badalias = None
462 self.unknowncmd = False
462 self.unknowncmd = False
463 self.source = source
463 self.source = source
464
464
465 try:
465 try:
466 aliases, entry = cmdutil.findcmd(self.name, cmdtable)
466 aliases, entry = cmdutil.findcmd(self.name, cmdtable)
467 for alias, e in cmdtable.iteritems():
467 for alias, e in cmdtable.iteritems():
468 if e is entry:
468 if e is entry:
469 self.cmd = alias
469 self.cmd = alias
470 break
470 break
471 self.shadows = True
471 self.shadows = True
472 except error.UnknownCommand:
472 except error.UnknownCommand:
473 self.shadows = False
473 self.shadows = False
474
474
475 if not self.definition:
475 if not self.definition:
476 self.badalias = _("no definition for alias '%s'") % self.name
476 self.badalias = _("no definition for alias '%s'") % self.name
477 return
477 return
478
478
479 if self.definition.startswith('!'):
479 if self.definition.startswith('!'):
480 shdef = self.definition[1:]
480 shdef = self.definition[1:]
481 self.shell = True
481 self.shell = True
482 def fn(ui, *args):
482 def fn(ui, *args):
483 env = {'HG_ARGS': ' '.join((self.name,) + args)}
483 env = {'HG_ARGS': ' '.join((self.name,) + args)}
484 def _checkvar(m):
484 def _checkvar(m):
485 if m.groups()[0] == '$':
485 if m.groups()[0] == '$':
486 return m.group()
486 return m.group()
487 elif int(m.groups()[0]) <= len(args):
487 elif int(m.groups()[0]) <= len(args):
488 return m.group()
488 return m.group()
489 else:
489 else:
490 ui.debug("No argument found for substitution "
490 ui.debug("No argument found for substitution "
491 "of %i variable in alias '%s' definition.\n"
491 "of %i variable in alias '%s' definition.\n"
492 % (int(m.groups()[0]), self.name))
492 % (int(m.groups()[0]), self.name))
493 return ''
493 return ''
494 cmd = re.sub(br'\$(\d+|\$)', _checkvar, shdef)
494 cmd = re.sub(br'\$(\d+|\$)', _checkvar, shdef)
495 cmd = aliasinterpolate(self.name, args, cmd)
495 cmd = aliasinterpolate(self.name, args, cmd)
496 return ui.system(cmd, environ=env,
496 return ui.system(cmd, environ=env,
497 blockedtag='alias_%s' % self.name)
497 blockedtag='alias_%s' % self.name)
498 self.fn = fn
498 self.fn = fn
499 self._populatehelp(ui, name, shdef, self.fn)
499 self._populatehelp(ui, name, shdef, self.fn)
500 return
500 return
501
501
502 try:
502 try:
503 args = pycompat.shlexsplit(self.definition)
503 args = pycompat.shlexsplit(self.definition)
504 except ValueError as inst:
504 except ValueError as inst:
505 self.badalias = (_("error in definition for alias '%s': %s")
505 self.badalias = (_("error in definition for alias '%s': %s")
506 % (self.name, stringutil.forcebytestr(inst)))
506 % (self.name, stringutil.forcebytestr(inst)))
507 return
507 return
508 earlyopts, args = _earlysplitopts(args)
508 earlyopts, args = _earlysplitopts(args)
509 if earlyopts:
509 if earlyopts:
510 self.badalias = (_("error in definition for alias '%s': %s may "
510 self.badalias = (_("error in definition for alias '%s': %s may "
511 "only be given on the command line")
511 "only be given on the command line")
512 % (self.name, '/'.join(pycompat.ziplist(*earlyopts)
512 % (self.name, '/'.join(pycompat.ziplist(*earlyopts)
513 [0])))
513 [0])))
514 return
514 return
515 self.cmdname = cmd = args.pop(0)
515 self.cmdname = cmd = args.pop(0)
516 self.givenargs = args
516 self.givenargs = args
517
517
518 try:
518 try:
519 tableentry = cmdutil.findcmd(cmd, cmdtable, False)[1]
519 tableentry = cmdutil.findcmd(cmd, cmdtable, False)[1]
520 if len(tableentry) > 2:
520 if len(tableentry) > 2:
521 self.fn, self.opts, cmdhelp = tableentry
521 self.fn, self.opts, cmdhelp = tableentry
522 else:
522 else:
523 self.fn, self.opts = tableentry
523 self.fn, self.opts = tableentry
524 cmdhelp = None
524 cmdhelp = None
525
525
526 self._populatehelp(ui, name, cmd, self.fn, cmdhelp)
526 self._populatehelp(ui, name, cmd, self.fn, cmdhelp)
527
527
528 except error.UnknownCommand:
528 except error.UnknownCommand:
529 self.badalias = (_("alias '%s' resolves to unknown command '%s'")
529 self.badalias = (_("alias '%s' resolves to unknown command '%s'")
530 % (self.name, cmd))
530 % (self.name, cmd))
531 self.unknowncmd = True
531 self.unknowncmd = True
532 except error.AmbiguousCommand:
532 except error.AmbiguousCommand:
533 self.badalias = (_("alias '%s' resolves to ambiguous command '%s'")
533 self.badalias = (_("alias '%s' resolves to ambiguous command '%s'")
534 % (self.name, cmd))
534 % (self.name, cmd))
535
535
536 def _populatehelp(self, ui, name, cmd, fn, defaulthelp=None):
536 def _populatehelp(self, ui, name, cmd, fn, defaulthelp=None):
537 # confine strings to be passed to i18n.gettext()
537 # confine strings to be passed to i18n.gettext()
538 cfg = {}
538 cfg = {}
539 for k in ('doc', 'help'):
539 for k in ('doc', 'help'):
540 v = ui.config('alias', '%s:%s' % (name, k), None)
540 v = ui.config('alias', '%s:%s' % (name, k), None)
541 if v is None:
541 if v is None:
542 continue
542 continue
543 if not encoding.isasciistr(v):
543 if not encoding.isasciistr(v):
544 self.badalias = (_("non-ASCII character in alias definition "
544 self.badalias = (_("non-ASCII character in alias definition "
545 "'%s:%s'") % (name, k))
545 "'%s:%s'") % (name, k))
546 return
546 return
547 cfg[k] = v
547 cfg[k] = v
548
548
549 self.help = cfg.get('help', defaulthelp or '')
549 self.help = cfg.get('help', defaulthelp or '')
550 if self.help and self.help.startswith("hg " + cmd):
550 if self.help and self.help.startswith("hg " + cmd):
551 # drop prefix in old-style help lines so hg shows the alias
551 # drop prefix in old-style help lines so hg shows the alias
552 self.help = self.help[4 + len(cmd):]
552 self.help = self.help[4 + len(cmd):]
553
553
554 doc = cfg.get('doc', pycompat.getdoc(fn))
554 doc = cfg.get('doc', pycompat.getdoc(fn))
555 if doc is not None:
555 if doc is not None:
556 doc = pycompat.sysstr(doc)
556 doc = pycompat.sysstr(doc)
557 self.__doc__ = doc
557 self.__doc__ = doc
558
558
559 @property
559 @property
560 def args(self):
560 def args(self):
561 args = pycompat.maplist(util.expandpath, self.givenargs)
561 args = pycompat.maplist(util.expandpath, self.givenargs)
562 return aliasargs(self.fn, args)
562 return aliasargs(self.fn, args)
563
563
564 def __getattr__(self, name):
564 def __getattr__(self, name):
565 adefaults = {r'norepo': True, r'intents': set(),
565 adefaults = {r'norepo': True, r'intents': set(),
566 r'optionalrepo': False, r'inferrepo': False}
566 r'optionalrepo': False, r'inferrepo': False}
567 if name not in adefaults:
567 if name not in adefaults:
568 raise AttributeError(name)
568 raise AttributeError(name)
569 if self.badalias or util.safehasattr(self, 'shell'):
569 if self.badalias or util.safehasattr(self, 'shell'):
570 return adefaults[name]
570 return adefaults[name]
571 return getattr(self.fn, name)
571 return getattr(self.fn, name)
572
572
573 def __call__(self, ui, *args, **opts):
573 def __call__(self, ui, *args, **opts):
574 if self.badalias:
574 if self.badalias:
575 hint = None
575 hint = None
576 if self.unknowncmd:
576 if self.unknowncmd:
577 try:
577 try:
578 # check if the command is in a disabled extension
578 # check if the command is in a disabled extension
579 cmd, ext = extensions.disabledcmd(ui, self.cmdname)[:2]
579 cmd, ext = extensions.disabledcmd(ui, self.cmdname)[:2]
580 hint = _("'%s' is provided by '%s' extension") % (cmd, ext)
580 hint = _("'%s' is provided by '%s' extension") % (cmd, ext)
581 except error.UnknownCommand:
581 except error.UnknownCommand:
582 pass
582 pass
583 raise error.Abort(self.badalias, hint=hint)
583 raise error.Abort(self.badalias, hint=hint)
584 if self.shadows:
584 if self.shadows:
585 ui.debug("alias '%s' shadows command '%s'\n" %
585 ui.debug("alias '%s' shadows command '%s'\n" %
586 (self.name, self.cmdname))
586 (self.name, self.cmdname))
587
587
588 ui.log('commandalias', "alias '%s' expands to '%s'\n",
588 ui.log('commandalias', "alias '%s' expands to '%s'\n",
589 self.name, self.definition)
589 self.name, self.definition)
590 if util.safehasattr(self, 'shell'):
590 if util.safehasattr(self, 'shell'):
591 return self.fn(ui, *args, **opts)
591 return self.fn(ui, *args, **opts)
592 else:
592 else:
593 try:
593 try:
594 return util.checksignature(self.fn)(ui, *args, **opts)
594 return util.checksignature(self.fn)(ui, *args, **opts)
595 except error.SignatureError:
595 except error.SignatureError:
596 args = ' '.join([self.cmdname] + self.args)
596 args = ' '.join([self.cmdname] + self.args)
597 ui.debug("alias '%s' expands to '%s'\n" % (self.name, args))
597 ui.debug("alias '%s' expands to '%s'\n" % (self.name, args))
598 raise
598 raise
599
599
600 class lazyaliasentry(object):
600 class lazyaliasentry(object):
601 """like a typical command entry (func, opts, help), but is lazy"""
601 """like a typical command entry (func, opts, help), but is lazy"""
602
602
603 def __init__(self, ui, name, definition, cmdtable, source):
603 def __init__(self, ui, name, definition, cmdtable, source):
604 self.ui = ui
604 self.ui = ui
605 self.name = name
605 self.name = name
606 self.definition = definition
606 self.definition = definition
607 self.cmdtable = cmdtable.copy()
607 self.cmdtable = cmdtable.copy()
608 self.source = source
608 self.source = source
609
609
610 @util.propertycache
610 @util.propertycache
611 def _aliasdef(self):
611 def _aliasdef(self):
612 return cmdalias(self.ui, self.name, self.definition, self.cmdtable,
612 return cmdalias(self.ui, self.name, self.definition, self.cmdtable,
613 self.source)
613 self.source)
614
614
615 def __getitem__(self, n):
615 def __getitem__(self, n):
616 aliasdef = self._aliasdef
616 aliasdef = self._aliasdef
617 if n == 0:
617 if n == 0:
618 return aliasdef
618 return aliasdef
619 elif n == 1:
619 elif n == 1:
620 return aliasdef.opts
620 return aliasdef.opts
621 elif n == 2:
621 elif n == 2:
622 return aliasdef.help
622 return aliasdef.help
623 else:
623 else:
624 raise IndexError
624 raise IndexError
625
625
626 def __iter__(self):
626 def __iter__(self):
627 for i in range(3):
627 for i in range(3):
628 yield self[i]
628 yield self[i]
629
629
630 def __len__(self):
630 def __len__(self):
631 return 3
631 return 3
632
632
633 def addaliases(ui, cmdtable):
633 def addaliases(ui, cmdtable):
634 # aliases are processed after extensions have been loaded, so they
634 # aliases are processed after extensions have been loaded, so they
635 # may use extension commands. Aliases can also use other alias definitions,
635 # may use extension commands. Aliases can also use other alias definitions,
636 # but only if they have been defined prior to the current definition.
636 # but only if they have been defined prior to the current definition.
637 for alias, definition in ui.configitems('alias', ignoresub=True):
637 for alias, definition in ui.configitems('alias', ignoresub=True):
638 try:
638 try:
639 if cmdtable[alias].definition == definition:
639 if cmdtable[alias].definition == definition:
640 continue
640 continue
641 except (KeyError, AttributeError):
641 except (KeyError, AttributeError):
642 # definition might not exist or it might not be a cmdalias
642 # definition might not exist or it might not be a cmdalias
643 pass
643 pass
644
644
645 source = ui.configsource('alias', alias)
645 source = ui.configsource('alias', alias)
646 entry = lazyaliasentry(ui, alias, definition, cmdtable, source)
646 entry = lazyaliasentry(ui, alias, definition, cmdtable, source)
647 cmdtable[alias] = entry
647 cmdtable[alias] = entry
648
648
649 def _parse(ui, args):
649 def _parse(ui, args):
650 options = {}
650 options = {}
651 cmdoptions = {}
651 cmdoptions = {}
652
652
653 try:
653 try:
654 args = fancyopts.fancyopts(args, commands.globalopts, options)
654 args = fancyopts.fancyopts(args, commands.globalopts, options)
655 except getopt.GetoptError as inst:
655 except getopt.GetoptError as inst:
656 raise error.CommandError(None, stringutil.forcebytestr(inst))
656 raise error.CommandError(None, stringutil.forcebytestr(inst))
657
657
658 if args:
658 if args:
659 cmd, args = args[0], args[1:]
659 cmd, args = args[0], args[1:]
660 aliases, entry = cmdutil.findcmd(cmd, commands.table,
660 aliases, entry = cmdutil.findcmd(cmd, commands.table,
661 ui.configbool("ui", "strict"))
661 ui.configbool("ui", "strict"))
662 cmd = aliases[0]
662 cmd = aliases[0]
663 args = aliasargs(entry[0], args)
663 args = aliasargs(entry[0], args)
664 defaults = ui.config("defaults", cmd)
664 defaults = ui.config("defaults", cmd)
665 if defaults:
665 if defaults:
666 args = pycompat.maplist(
666 args = pycompat.maplist(
667 util.expandpath, pycompat.shlexsplit(defaults)) + args
667 util.expandpath, pycompat.shlexsplit(defaults)) + args
668 c = list(entry[1])
668 c = list(entry[1])
669 else:
669 else:
670 cmd = None
670 cmd = None
671 c = []
671 c = []
672
672
673 # combine global options into local
673 # combine global options into local
674 for o in commands.globalopts:
674 for o in commands.globalopts:
675 c.append((o[0], o[1], options[o[1]], o[3]))
675 c.append((o[0], o[1], options[o[1]], o[3]))
676
676
677 try:
677 try:
678 args = fancyopts.fancyopts(args, c, cmdoptions, gnu=True)
678 args = fancyopts.fancyopts(args, c, cmdoptions, gnu=True)
679 except getopt.GetoptError as inst:
679 except getopt.GetoptError as inst:
680 raise error.CommandError(cmd, stringutil.forcebytestr(inst))
680 raise error.CommandError(cmd, stringutil.forcebytestr(inst))
681
681
682 # separate global options back out
682 # separate global options back out
683 for o in commands.globalopts:
683 for o in commands.globalopts:
684 n = o[1]
684 n = o[1]
685 options[n] = cmdoptions[n]
685 options[n] = cmdoptions[n]
686 del cmdoptions[n]
686 del cmdoptions[n]
687
687
688 return (cmd, cmd and entry[0] or None, args, options, cmdoptions)
688 return (cmd, cmd and entry[0] or None, args, options, cmdoptions)
689
689
690 def _parseconfig(ui, config):
690 def _parseconfig(ui, config):
691 """parse the --config options from the command line"""
691 """parse the --config options from the command line"""
692 configs = []
692 configs = []
693
693
694 for cfg in config:
694 for cfg in config:
695 try:
695 try:
696 name, value = [cfgelem.strip()
696 name, value = [cfgelem.strip()
697 for cfgelem in cfg.split('=', 1)]
697 for cfgelem in cfg.split('=', 1)]
698 section, name = name.split('.', 1)
698 section, name = name.split('.', 1)
699 if not section or not name:
699 if not section or not name:
700 raise IndexError
700 raise IndexError
701 ui.setconfig(section, name, value, '--config')
701 ui.setconfig(section, name, value, '--config')
702 configs.append((section, name, value))
702 configs.append((section, name, value))
703 except (IndexError, ValueError):
703 except (IndexError, ValueError):
704 raise error.Abort(_('malformed --config option: %r '
704 raise error.Abort(_('malformed --config option: %r '
705 '(use --config section.name=value)')
705 '(use --config section.name=value)')
706 % pycompat.bytestr(cfg))
706 % pycompat.bytestr(cfg))
707
707
708 return configs
708 return configs
709
709
710 def _earlyparseopts(ui, args):
710 def _earlyparseopts(ui, args):
711 options = {}
711 options = {}
712 fancyopts.fancyopts(args, commands.globalopts, options,
712 fancyopts.fancyopts(args, commands.globalopts, options,
713 gnu=not ui.plain('strictflags'), early=True,
713 gnu=not ui.plain('strictflags'), early=True,
714 optaliases={'repository': ['repo']})
714 optaliases={'repository': ['repo']})
715 return options
715 return options
716
716
717 def _earlysplitopts(args):
717 def _earlysplitopts(args):
718 """Split args into a list of possible early options and remainder args"""
718 """Split args into a list of possible early options and remainder args"""
719 shortoptions = 'R:'
719 shortoptions = 'R:'
720 # TODO: perhaps 'debugger' should be included
720 # TODO: perhaps 'debugger' should be included
721 longoptions = ['cwd=', 'repository=', 'repo=', 'config=']
721 longoptions = ['cwd=', 'repository=', 'repo=', 'config=']
722 return fancyopts.earlygetopt(args, shortoptions, longoptions,
722 return fancyopts.earlygetopt(args, shortoptions, longoptions,
723 gnu=True, keepsep=True)
723 gnu=True, keepsep=True)
724
724
725 def runcommand(lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions):
725 def runcommand(lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions):
726 # run pre-hook, and abort if it fails
726 # run pre-hook, and abort if it fails
727 hook.hook(lui, repo, "pre-%s" % cmd, True, args=" ".join(fullargs),
727 hook.hook(lui, repo, "pre-%s" % cmd, True, args=" ".join(fullargs),
728 pats=cmdpats, opts=cmdoptions)
728 pats=cmdpats, opts=cmdoptions)
729 try:
729 try:
730 ret = _runcommand(ui, options, cmd, d)
730 ret = _runcommand(ui, options, cmd, d)
731 # run post-hook, passing command result
731 # run post-hook, passing command result
732 hook.hook(lui, repo, "post-%s" % cmd, False, args=" ".join(fullargs),
732 hook.hook(lui, repo, "post-%s" % cmd, False, args=" ".join(fullargs),
733 result=ret, pats=cmdpats, opts=cmdoptions)
733 result=ret, pats=cmdpats, opts=cmdoptions)
734 except Exception:
734 except Exception:
735 # run failure hook and re-raise
735 # run failure hook and re-raise
736 hook.hook(lui, repo, "fail-%s" % cmd, False, args=" ".join(fullargs),
736 hook.hook(lui, repo, "fail-%s" % cmd, False, args=" ".join(fullargs),
737 pats=cmdpats, opts=cmdoptions)
737 pats=cmdpats, opts=cmdoptions)
738 raise
738 raise
739 return ret
739 return ret
740
740
741 def _getlocal(ui, rpath, wd=None):
741 def _getlocal(ui, rpath, wd=None):
742 """Return (path, local ui object) for the given target path.
742 """Return (path, local ui object) for the given target path.
743
743
744 Takes paths in [cwd]/.hg/hgrc into account."
744 Takes paths in [cwd]/.hg/hgrc into account."
745 """
745 """
746 if wd is None:
746 if wd is None:
747 try:
747 try:
748 wd = pycompat.getcwd()
748 wd = pycompat.getcwd()
749 except OSError as e:
749 except OSError as e:
750 raise error.Abort(_("error getting current working directory: %s") %
750 raise error.Abort(_("error getting current working directory: %s") %
751 encoding.strtolocal(e.strerror))
751 encoding.strtolocal(e.strerror))
752 path = cmdutil.findrepo(wd) or ""
752 path = cmdutil.findrepo(wd) or ""
753 if not path:
753 if not path:
754 lui = ui
754 lui = ui
755 else:
755 else:
756 lui = ui.copy()
756 lui = ui.copy()
757 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
757 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
758
758
759 if rpath:
759 if rpath:
760 path = lui.expandpath(rpath)
760 path = lui.expandpath(rpath)
761 lui = ui.copy()
761 lui = ui.copy()
762 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
762 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
763
763
764 return path, lui
764 return path, lui
765
765
766 def _checkshellalias(lui, ui, args):
766 def _checkshellalias(lui, ui, args):
767 """Return the function to run the shell alias, if it is required"""
767 """Return the function to run the shell alias, if it is required"""
768 options = {}
768 options = {}
769
769
770 try:
770 try:
771 args = fancyopts.fancyopts(args, commands.globalopts, options)
771 args = fancyopts.fancyopts(args, commands.globalopts, options)
772 except getopt.GetoptError:
772 except getopt.GetoptError:
773 return
773 return
774
774
775 if not args:
775 if not args:
776 return
776 return
777
777
778 cmdtable = commands.table
778 cmdtable = commands.table
779
779
780 cmd = args[0]
780 cmd = args[0]
781 try:
781 try:
782 strict = ui.configbool("ui", "strict")
782 strict = ui.configbool("ui", "strict")
783 aliases, entry = cmdutil.findcmd(cmd, cmdtable, strict)
783 aliases, entry = cmdutil.findcmd(cmd, cmdtable, strict)
784 except (error.AmbiguousCommand, error.UnknownCommand):
784 except (error.AmbiguousCommand, error.UnknownCommand):
785 return
785 return
786
786
787 cmd = aliases[0]
787 cmd = aliases[0]
788 fn = entry[0]
788 fn = entry[0]
789
789
790 if cmd and util.safehasattr(fn, 'shell'):
790 if cmd and util.safehasattr(fn, 'shell'):
791 # shell alias shouldn't receive early options which are consumed by hg
791 # shell alias shouldn't receive early options which are consumed by hg
792 _earlyopts, args = _earlysplitopts(args)
792 _earlyopts, args = _earlysplitopts(args)
793 d = lambda: fn(ui, *args[1:])
793 d = lambda: fn(ui, *args[1:])
794 return lambda: runcommand(lui, None, cmd, args[:1], ui, options, d,
794 return lambda: runcommand(lui, None, cmd, args[:1], ui, options, d,
795 [], {})
795 [], {})
796
796
797 def _dispatch(req):
797 def _dispatch(req):
798 args = req.args
798 args = req.args
799 ui = req.ui
799 ui = req.ui
800
800
801 # check for cwd
801 # check for cwd
802 cwd = req.earlyoptions['cwd']
802 cwd = req.earlyoptions['cwd']
803 if cwd:
803 if cwd:
804 os.chdir(cwd)
804 os.chdir(cwd)
805
805
806 rpath = req.earlyoptions['repository']
806 rpath = req.earlyoptions['repository']
807 path, lui = _getlocal(ui, rpath)
807 path, lui = _getlocal(ui, rpath)
808
808
809 uis = {ui, lui}
809 uis = {ui, lui}
810
810
811 if req.repo:
811 if req.repo:
812 uis.add(req.repo.ui)
812 uis.add(req.repo.ui)
813
813
814 if (req.earlyoptions['verbose'] or req.earlyoptions['debug']
814 if (req.earlyoptions['verbose'] or req.earlyoptions['debug']
815 or req.earlyoptions['quiet']):
815 or req.earlyoptions['quiet']):
816 for opt in ('verbose', 'debug', 'quiet'):
816 for opt in ('verbose', 'debug', 'quiet'):
817 val = pycompat.bytestr(bool(req.earlyoptions[opt]))
817 val = pycompat.bytestr(bool(req.earlyoptions[opt]))
818 for ui_ in uis:
818 for ui_ in uis:
819 ui_.setconfig('ui', opt, val, '--' + opt)
819 ui_.setconfig('ui', opt, val, '--' + opt)
820
820
821 if req.earlyoptions['profile']:
821 if req.earlyoptions['profile']:
822 for ui_ in uis:
822 for ui_ in uis:
823 ui_.setconfig('profiling', 'enabled', 'true', '--profile')
823 ui_.setconfig('profiling', 'enabled', 'true', '--profile')
824
824
825 profile = lui.configbool('profiling', 'enabled')
825 profile = lui.configbool('profiling', 'enabled')
826 with profiling.profile(lui, enabled=profile) as profiler:
826 with profiling.profile(lui, enabled=profile) as profiler:
827 # Configure extensions in phases: uisetup, extsetup, cmdtable, and
827 # Configure extensions in phases: uisetup, extsetup, cmdtable, and
828 # reposetup
828 # reposetup
829 extensions.loadall(lui)
829 extensions.loadall(lui)
830 # Propagate any changes to lui.__class__ by extensions
830 # Propagate any changes to lui.__class__ by extensions
831 ui.__class__ = lui.__class__
831 ui.__class__ = lui.__class__
832
832
833 # (uisetup and extsetup are handled in extensions.loadall)
833 # (uisetup and extsetup are handled in extensions.loadall)
834
834
835 # (reposetup is handled in hg.repository)
835 # (reposetup is handled in hg.repository)
836
836
837 addaliases(lui, commands.table)
837 addaliases(lui, commands.table)
838
838
839 # All aliases and commands are completely defined, now.
839 # All aliases and commands are completely defined, now.
840 # Check abbreviation/ambiguity of shell alias.
840 # Check abbreviation/ambiguity of shell alias.
841 shellaliasfn = _checkshellalias(lui, ui, args)
841 shellaliasfn = _checkshellalias(lui, ui, args)
842 if shellaliasfn:
842 if shellaliasfn:
843 return shellaliasfn()
843 return shellaliasfn()
844
844
845 # check for fallback encoding
845 # check for fallback encoding
846 fallback = lui.config('ui', 'fallbackencoding')
846 fallback = lui.config('ui', 'fallbackencoding')
847 if fallback:
847 if fallback:
848 encoding.fallbackencoding = fallback
848 encoding.fallbackencoding = fallback
849
849
850 fullargs = args
850 fullargs = args
851 cmd, func, args, options, cmdoptions = _parse(lui, args)
851 cmd, func, args, options, cmdoptions = _parse(lui, args)
852
852
853 if options["config"] != req.earlyoptions["config"]:
853 if options["config"] != req.earlyoptions["config"]:
854 raise error.Abort(_("option --config may not be abbreviated!"))
854 raise error.Abort(_("option --config may not be abbreviated!"))
855 if options["cwd"] != req.earlyoptions["cwd"]:
855 if options["cwd"] != req.earlyoptions["cwd"]:
856 raise error.Abort(_("option --cwd may not be abbreviated!"))
856 raise error.Abort(_("option --cwd may not be abbreviated!"))
857 if options["repository"] != req.earlyoptions["repository"]:
857 if options["repository"] != req.earlyoptions["repository"]:
858 raise error.Abort(_(
858 raise error.Abort(_(
859 "option -R has to be separated from other options (e.g. not "
859 "option -R has to be separated from other options (e.g. not "
860 "-qR) and --repository may only be abbreviated as --repo!"))
860 "-qR) and --repository may only be abbreviated as --repo!"))
861 if options["debugger"] != req.earlyoptions["debugger"]:
861 if options["debugger"] != req.earlyoptions["debugger"]:
862 raise error.Abort(_("option --debugger may not be abbreviated!"))
862 raise error.Abort(_("option --debugger may not be abbreviated!"))
863 # don't validate --profile/--traceback, which can be enabled from now
863 # don't validate --profile/--traceback, which can be enabled from now
864
864
865 if options["encoding"]:
865 if options["encoding"]:
866 encoding.encoding = options["encoding"]
866 encoding.encoding = options["encoding"]
867 if options["encodingmode"]:
867 if options["encodingmode"]:
868 encoding.encodingmode = options["encodingmode"]
868 encoding.encodingmode = options["encodingmode"]
869 if options["time"]:
869 if options["time"]:
870 def get_times():
870 def get_times():
871 t = os.times()
871 t = os.times()
872 if t[4] == 0.0:
872 if t[4] == 0.0:
873 # Windows leaves this as zero, so use time.clock()
873 # Windows leaves this as zero, so use time.clock()
874 t = (t[0], t[1], t[2], t[3], time.clock())
874 t = (t[0], t[1], t[2], t[3], time.clock())
875 return t
875 return t
876 s = get_times()
876 s = get_times()
877 def print_time():
877 def print_time():
878 t = get_times()
878 t = get_times()
879 ui.warn(
879 ui.warn(
880 _("time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
880 _("time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
881 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
881 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
882 ui.atexit(print_time)
882 ui.atexit(print_time)
883 if options["profile"]:
883 if options["profile"]:
884 profiler.start()
884 profiler.start()
885
885
886 # if abbreviated version of this were used, take them in account, now
886 # if abbreviated version of this were used, take them in account, now
887 if options['verbose'] or options['debug'] or options['quiet']:
887 if options['verbose'] or options['debug'] or options['quiet']:
888 for opt in ('verbose', 'debug', 'quiet'):
888 for opt in ('verbose', 'debug', 'quiet'):
889 if options[opt] == req.earlyoptions[opt]:
889 if options[opt] == req.earlyoptions[opt]:
890 continue
890 continue
891 val = pycompat.bytestr(bool(options[opt]))
891 val = pycompat.bytestr(bool(options[opt]))
892 for ui_ in uis:
892 for ui_ in uis:
893 ui_.setconfig('ui', opt, val, '--' + opt)
893 ui_.setconfig('ui', opt, val, '--' + opt)
894
894
895 if options['traceback']:
895 if options['traceback']:
896 for ui_ in uis:
896 for ui_ in uis:
897 ui_.setconfig('ui', 'traceback', 'on', '--traceback')
897 ui_.setconfig('ui', 'traceback', 'on', '--traceback')
898
898
899 if options['noninteractive']:
899 if options['noninteractive']:
900 for ui_ in uis:
900 for ui_ in uis:
901 ui_.setconfig('ui', 'interactive', 'off', '-y')
901 ui_.setconfig('ui', 'interactive', 'off', '-y')
902
902
903 if cmdoptions.get('insecure', False):
903 if cmdoptions.get('insecure', False):
904 for ui_ in uis:
904 for ui_ in uis:
905 ui_.insecureconnections = True
905 ui_.insecureconnections = True
906
906
907 # setup color handling before pager, because setting up pager
907 # setup color handling before pager, because setting up pager
908 # might cause incorrect console information
908 # might cause incorrect console information
909 coloropt = options['color']
909 coloropt = options['color']
910 for ui_ in uis:
910 for ui_ in uis:
911 if coloropt:
911 if coloropt:
912 ui_.setconfig('ui', 'color', coloropt, '--color')
912 ui_.setconfig('ui', 'color', coloropt, '--color')
913 color.setup(ui_)
913 color.setup(ui_)
914
914
915 if stringutil.parsebool(options['pager']):
915 if stringutil.parsebool(options['pager']):
916 # ui.pager() expects 'internal-always-' prefix in this case
916 # ui.pager() expects 'internal-always-' prefix in this case
917 ui.pager('internal-always-' + cmd)
917 ui.pager('internal-always-' + cmd)
918 elif options['pager'] != 'auto':
918 elif options['pager'] != 'auto':
919 for ui_ in uis:
919 for ui_ in uis:
920 ui_.disablepager()
920 ui_.disablepager()
921
921
922 if options['version']:
922 if options['version']:
923 return commands.version_(ui)
923 return commands.version_(ui)
924 if options['help']:
924 if options['help']:
925 return commands.help_(ui, cmd, command=cmd is not None)
925 return commands.help_(ui, cmd, command=cmd is not None)
926 elif not cmd:
926 elif not cmd:
927 return commands.help_(ui, 'shortlist')
927 return commands.help_(ui, 'shortlist')
928
928
929 repo = None
929 repo = None
930 cmdpats = args[:]
930 cmdpats = args[:]
931 if not func.norepo:
931 if not func.norepo:
932 # use the repo from the request only if we don't have -R
932 # use the repo from the request only if we don't have -R
933 if not rpath and not cwd:
933 if not rpath and not cwd:
934 repo = req.repo
934 repo = req.repo
935
935
936 if repo:
936 if repo:
937 # set the descriptors of the repo ui to those of ui
937 # set the descriptors of the repo ui to those of ui
938 repo.ui.fin = ui.fin
938 repo.ui.fin = ui.fin
939 repo.ui.fout = ui.fout
939 repo.ui.fout = ui.fout
940 repo.ui.ferr = ui.ferr
940 repo.ui.ferr = ui.ferr
941 else:
941 else:
942 try:
942 try:
943 repo = hg.repository(ui, path=path,
943 repo = hg.repository(ui, path=path,
944 presetupfuncs=req.prereposetups,
944 presetupfuncs=req.prereposetups,
945 intents=func.intents)
945 intents=func.intents)
946 if not repo.local():
946 if not repo.local():
947 raise error.Abort(_("repository '%s' is not local")
947 raise error.Abort(_("repository '%s' is not local")
948 % path)
948 % path)
949 repo.ui.setconfig("bundle", "mainreporoot", repo.root,
949 repo.ui.setconfig("bundle", "mainreporoot", repo.root,
950 'repo')
950 'repo')
951 except error.RequirementError:
951 except error.RequirementError:
952 raise
952 raise
953 except error.RepoError:
953 except error.RepoError:
954 if rpath: # invalid -R path
954 if rpath: # invalid -R path
955 raise
955 raise
956 if not func.optionalrepo:
956 if not func.optionalrepo:
957 if func.inferrepo and args and not path:
957 if func.inferrepo and args and not path:
958 # try to infer -R from command args
958 # try to infer -R from command args
959 repos = pycompat.maplist(cmdutil.findrepo, args)
959 repos = pycompat.maplist(cmdutil.findrepo, args)
960 guess = repos[0]
960 guess = repos[0]
961 if guess and repos.count(guess) == len(repos):
961 if guess and repos.count(guess) == len(repos):
962 req.args = ['--repository', guess] + fullargs
962 req.args = ['--repository', guess] + fullargs
963 req.earlyoptions['repository'] = guess
963 req.earlyoptions['repository'] = guess
964 return _dispatch(req)
964 return _dispatch(req)
965 if not path:
965 if not path:
966 raise error.RepoError(_("no repository found in"
966 raise error.RepoError(_("no repository found in"
967 " '%s' (.hg not found)")
967 " '%s' (.hg not found)")
968 % pycompat.getcwd())
968 % pycompat.getcwd())
969 raise
969 raise
970 if repo:
970 if repo:
971 ui = repo.ui
971 ui = repo.ui
972 if options['hidden']:
972 if options['hidden']:
973 repo = repo.unfiltered()
973 repo = repo.unfiltered()
974 args.insert(0, repo)
974 args.insert(0, repo)
975 elif rpath:
975 elif rpath:
976 ui.warn(_("warning: --repository ignored\n"))
976 ui.warn(_("warning: --repository ignored\n"))
977
977
978 msg = _formatargs(fullargs)
978 msg = _formatargs(fullargs)
979 ui.log("command", '%s\n', msg)
979 ui.log("command", '%s\n', msg)
980 strcmdopt = pycompat.strkwargs(cmdoptions)
980 strcmdopt = pycompat.strkwargs(cmdoptions)
981 d = lambda: util.checksignature(func)(ui, *args, **strcmdopt)
981 d = lambda: util.checksignature(func)(ui, *args, **strcmdopt)
982 try:
982 try:
983 return runcommand(lui, repo, cmd, fullargs, ui, options, d,
983 return runcommand(lui, repo, cmd, fullargs, ui, options, d,
984 cmdpats, cmdoptions)
984 cmdpats, cmdoptions)
985 finally:
985 finally:
986 if repo and repo != req.repo:
986 if repo and repo != req.repo:
987 repo.close()
987 repo.close()
988
988
989 def _runcommand(ui, options, cmd, cmdfunc):
989 def _runcommand(ui, options, cmd, cmdfunc):
990 """Run a command function, possibly with profiling enabled."""
990 """Run a command function, possibly with profiling enabled."""
991 try:
991 try:
992 return cmdfunc()
992 return cmdfunc()
993 except error.SignatureError:
993 except error.SignatureError:
994 raise error.CommandError(cmd, _('invalid arguments'))
994 raise error.CommandError(cmd, _('invalid arguments'))
995
995
996 def _exceptionwarning(ui):
996 def _exceptionwarning(ui):
997 """Produce a warning message for the current active exception"""
997 """Produce a warning message for the current active exception"""
998
998
999 # For compatibility checking, we discard the portion of the hg
999 # For compatibility checking, we discard the portion of the hg
1000 # version after the + on the assumption that if a "normal
1000 # version after the + on the assumption that if a "normal
1001 # user" is running a build with a + in it the packager
1001 # user" is running a build with a + in it the packager
1002 # probably built from fairly close to a tag and anyone with a
1002 # probably built from fairly close to a tag and anyone with a
1003 # 'make local' copy of hg (where the version number can be out
1003 # 'make local' copy of hg (where the version number can be out
1004 # of date) will be clueful enough to notice the implausible
1004 # of date) will be clueful enough to notice the implausible
1005 # version number and try updating.
1005 # version number and try updating.
1006 ct = util.versiontuple(n=2)
1006 ct = util.versiontuple(n=2)
1007 worst = None, ct, ''
1007 worst = None, ct, ''
1008 if ui.config('ui', 'supportcontact') is None:
1008 if ui.config('ui', 'supportcontact') is None:
1009 for name, mod in extensions.extensions():
1009 for name, mod in extensions.extensions():
1010 # 'testedwith' should be bytes, but not all extensions are ported
1010 # 'testedwith' should be bytes, but not all extensions are ported
1011 # to py3 and we don't want UnicodeException because of that.
1011 # to py3 and we don't want UnicodeException because of that.
1012 testedwith = stringutil.forcebytestr(getattr(mod, 'testedwith', ''))
1012 testedwith = stringutil.forcebytestr(getattr(mod, 'testedwith', ''))
1013 report = getattr(mod, 'buglink', _('the extension author.'))
1013 report = getattr(mod, 'buglink', _('the extension author.'))
1014 if not testedwith.strip():
1014 if not testedwith.strip():
1015 # We found an untested extension. It's likely the culprit.
1015 # We found an untested extension. It's likely the culprit.
1016 worst = name, 'unknown', report
1016 worst = name, 'unknown', report
1017 break
1017 break
1018
1018
1019 # Never blame on extensions bundled with Mercurial.
1019 # Never blame on extensions bundled with Mercurial.
1020 if extensions.ismoduleinternal(mod):
1020 if extensions.ismoduleinternal(mod):
1021 continue
1021 continue
1022
1022
1023 tested = [util.versiontuple(t, 2) for t in testedwith.split()]
1023 tested = [util.versiontuple(t, 2) for t in testedwith.split()]
1024 if ct in tested:
1024 if ct in tested:
1025 continue
1025 continue
1026
1026
1027 lower = [t for t in tested if t < ct]
1027 lower = [t for t in tested if t < ct]
1028 nearest = max(lower or tested)
1028 nearest = max(lower or tested)
1029 if worst[0] is None or nearest < worst[1]:
1029 if worst[0] is None or nearest < worst[1]:
1030 worst = name, nearest, report
1030 worst = name, nearest, report
1031 if worst[0] is not None:
1031 if worst[0] is not None:
1032 name, testedwith, report = worst
1032 name, testedwith, report = worst
1033 if not isinstance(testedwith, (bytes, str)):
1033 if not isinstance(testedwith, (bytes, str)):
1034 testedwith = '.'.join([stringutil.forcebytestr(c)
1034 testedwith = '.'.join([stringutil.forcebytestr(c)
1035 for c in testedwith])
1035 for c in testedwith])
1036 warning = (_('** Unknown exception encountered with '
1036 warning = (_('** Unknown exception encountered with '
1037 'possibly-broken third-party extension %s\n'
1037 'possibly-broken third-party extension %s\n'
1038 '** which supports versions %s of Mercurial.\n'
1038 '** which supports versions %s of Mercurial.\n'
1039 '** Please disable %s and try your action again.\n'
1039 '** Please disable %s and try your action again.\n'
1040 '** If that fixes the bug please report it to %s\n')
1040 '** If that fixes the bug please report it to %s\n')
1041 % (name, testedwith, name, stringutil.forcebytestr(report)))
1041 % (name, testedwith, name, stringutil.forcebytestr(report)))
1042 else:
1042 else:
1043 bugtracker = ui.config('ui', 'supportcontact')
1043 bugtracker = ui.config('ui', 'supportcontact')
1044 if bugtracker is None:
1044 if bugtracker is None:
1045 bugtracker = _("https://mercurial-scm.org/wiki/BugTracker")
1045 bugtracker = _("https://mercurial-scm.org/wiki/BugTracker")
1046 warning = (_("** unknown exception encountered, "
1046 warning = (_("** unknown exception encountered, "
1047 "please report by visiting\n** ") + bugtracker + '\n')
1047 "please report by visiting\n** ") + bugtracker + '\n')
1048 sysversion = pycompat.sysbytes(sys.version).replace('\n', '')
1048 sysversion = pycompat.sysbytes(sys.version).replace('\n', '')
1049 warning += ((_("** Python %s\n") % sysversion) +
1049 warning += ((_("** Python %s\n") % sysversion) +
1050 (_("** Mercurial Distributed SCM (version %s)\n") %
1050 (_("** Mercurial Distributed SCM (version %s)\n") %
1051 util.version()) +
1051 util.version()) +
1052 (_("** Extensions loaded: %s\n") %
1052 (_("** Extensions loaded: %s\n") %
1053 ", ".join([x[0] for x in extensions.extensions()])))
1053 ", ".join([x[0] for x in extensions.extensions()])))
1054 return warning
1054 return warning
1055
1055
1056 def handlecommandexception(ui):
1056 def handlecommandexception(ui):
1057 """Produce a warning message for broken commands
1057 """Produce a warning message for broken commands
1058
1058
1059 Called when handling an exception; the exception is reraised if
1059 Called when handling an exception; the exception is reraised if
1060 this function returns False, ignored otherwise.
1060 this function returns False, ignored otherwise.
1061 """
1061 """
1062 warning = _exceptionwarning(ui)
1062 warning = _exceptionwarning(ui)
1063 ui.log("commandexception", "%s\n%s\n", warning,
1063 ui.log("commandexception", "%s\n%s\n", warning,
1064 pycompat.sysbytes(traceback.format_exc()))
1064 pycompat.sysbytes(traceback.format_exc()))
1065 ui.warn(warning)
1065 ui.warn(warning)
1066 return False # re-raise the exception
1066 return False # re-raise the exception
@@ -1,1700 +1,1700 b''
1 # scmutil.py - Mercurial core utility functions
1 # scmutil.py - Mercurial core utility functions
2 #
2 #
3 # Copyright Matt Mackall <mpm@selenic.com>
3 # Copyright Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import errno
10 import errno
11 import glob
11 import glob
12 import hashlib
12 import hashlib
13 import os
13 import os
14 import re
14 import re
15 import socket
15 import socket
16 import subprocess
16 import subprocess
17 import weakref
17 import weakref
18
18
19 from .i18n import _
19 from .i18n import _
20 from .node import (
20 from .node import (
21 bin,
21 bin,
22 hex,
22 hex,
23 nullid,
23 nullid,
24 short,
24 short,
25 wdirid,
25 wdirid,
26 wdirrev,
26 wdirrev,
27 )
27 )
28
28
29 from . import (
29 from . import (
30 encoding,
30 encoding,
31 error,
31 error,
32 match as matchmod,
32 match as matchmod,
33 obsolete,
33 obsolete,
34 obsutil,
34 obsutil,
35 pathutil,
35 pathutil,
36 phases,
36 phases,
37 pycompat,
37 pycompat,
38 revsetlang,
38 revsetlang,
39 similar,
39 similar,
40 url,
40 url,
41 util,
41 util,
42 vfs,
42 vfs,
43 )
43 )
44
44
45 from .utils import (
45 from .utils import (
46 procutil,
46 procutil,
47 stringutil,
47 stringutil,
48 )
48 )
49
49
50 if pycompat.iswindows:
50 if pycompat.iswindows:
51 from . import scmwindows as scmplatform
51 from . import scmwindows as scmplatform
52 else:
52 else:
53 from . import scmposix as scmplatform
53 from . import scmposix as scmplatform
54
54
55 termsize = scmplatform.termsize
55 termsize = scmplatform.termsize
56
56
57 class status(tuple):
57 class status(tuple):
58 '''Named tuple with a list of files per status. The 'deleted', 'unknown'
58 '''Named tuple with a list of files per status. The 'deleted', 'unknown'
59 and 'ignored' properties are only relevant to the working copy.
59 and 'ignored' properties are only relevant to the working copy.
60 '''
60 '''
61
61
62 __slots__ = ()
62 __slots__ = ()
63
63
64 def __new__(cls, modified, added, removed, deleted, unknown, ignored,
64 def __new__(cls, modified, added, removed, deleted, unknown, ignored,
65 clean):
65 clean):
66 return tuple.__new__(cls, (modified, added, removed, deleted, unknown,
66 return tuple.__new__(cls, (modified, added, removed, deleted, unknown,
67 ignored, clean))
67 ignored, clean))
68
68
69 @property
69 @property
70 def modified(self):
70 def modified(self):
71 '''files that have been modified'''
71 '''files that have been modified'''
72 return self[0]
72 return self[0]
73
73
74 @property
74 @property
75 def added(self):
75 def added(self):
76 '''files that have been added'''
76 '''files that have been added'''
77 return self[1]
77 return self[1]
78
78
79 @property
79 @property
80 def removed(self):
80 def removed(self):
81 '''files that have been removed'''
81 '''files that have been removed'''
82 return self[2]
82 return self[2]
83
83
84 @property
84 @property
85 def deleted(self):
85 def deleted(self):
86 '''files that are in the dirstate, but have been deleted from the
86 '''files that are in the dirstate, but have been deleted from the
87 working copy (aka "missing")
87 working copy (aka "missing")
88 '''
88 '''
89 return self[3]
89 return self[3]
90
90
91 @property
91 @property
92 def unknown(self):
92 def unknown(self):
93 '''files not in the dirstate that are not ignored'''
93 '''files not in the dirstate that are not ignored'''
94 return self[4]
94 return self[4]
95
95
96 @property
96 @property
97 def ignored(self):
97 def ignored(self):
98 '''files not in the dirstate that are ignored (by _dirignore())'''
98 '''files not in the dirstate that are ignored (by _dirignore())'''
99 return self[5]
99 return self[5]
100
100
101 @property
101 @property
102 def clean(self):
102 def clean(self):
103 '''files that have not been modified'''
103 '''files that have not been modified'''
104 return self[6]
104 return self[6]
105
105
106 def __repr__(self, *args, **kwargs):
106 def __repr__(self, *args, **kwargs):
107 return ((r'<status modified=%s, added=%s, removed=%s, deleted=%s, '
107 return ((r'<status modified=%s, added=%s, removed=%s, deleted=%s, '
108 r'unknown=%s, ignored=%s, clean=%s>') %
108 r'unknown=%s, ignored=%s, clean=%s>') %
109 tuple(pycompat.sysstr(stringutil.pprint(v)) for v in self))
109 tuple(pycompat.sysstr(stringutil.pprint(v)) for v in self))
110
110
111 def itersubrepos(ctx1, ctx2):
111 def itersubrepos(ctx1, ctx2):
112 """find subrepos in ctx1 or ctx2"""
112 """find subrepos in ctx1 or ctx2"""
113 # Create a (subpath, ctx) mapping where we prefer subpaths from
113 # Create a (subpath, ctx) mapping where we prefer subpaths from
114 # ctx1. The subpaths from ctx2 are important when the .hgsub file
114 # ctx1. The subpaths from ctx2 are important when the .hgsub file
115 # has been modified (in ctx2) but not yet committed (in ctx1).
115 # has been modified (in ctx2) but not yet committed (in ctx1).
116 subpaths = dict.fromkeys(ctx2.substate, ctx2)
116 subpaths = dict.fromkeys(ctx2.substate, ctx2)
117 subpaths.update(dict.fromkeys(ctx1.substate, ctx1))
117 subpaths.update(dict.fromkeys(ctx1.substate, ctx1))
118
118
119 missing = set()
119 missing = set()
120
120
121 for subpath in ctx2.substate:
121 for subpath in ctx2.substate:
122 if subpath not in ctx1.substate:
122 if subpath not in ctx1.substate:
123 del subpaths[subpath]
123 del subpaths[subpath]
124 missing.add(subpath)
124 missing.add(subpath)
125
125
126 for subpath, ctx in sorted(subpaths.iteritems()):
126 for subpath, ctx in sorted(subpaths.iteritems()):
127 yield subpath, ctx.sub(subpath)
127 yield subpath, ctx.sub(subpath)
128
128
129 # Yield an empty subrepo based on ctx1 for anything only in ctx2. That way,
129 # Yield an empty subrepo based on ctx1 for anything only in ctx2. That way,
130 # status and diff will have an accurate result when it does
130 # status and diff will have an accurate result when it does
131 # 'sub.{status|diff}(rev2)'. Otherwise, the ctx2 subrepo is compared
131 # 'sub.{status|diff}(rev2)'. Otherwise, the ctx2 subrepo is compared
132 # against itself.
132 # against itself.
133 for subpath in missing:
133 for subpath in missing:
134 yield subpath, ctx2.nullsub(subpath, ctx1)
134 yield subpath, ctx2.nullsub(subpath, ctx1)
135
135
136 def nochangesfound(ui, repo, excluded=None):
136 def nochangesfound(ui, repo, excluded=None):
137 '''Report no changes for push/pull, excluded is None or a list of
137 '''Report no changes for push/pull, excluded is None or a list of
138 nodes excluded from the push/pull.
138 nodes excluded from the push/pull.
139 '''
139 '''
140 secretlist = []
140 secretlist = []
141 if excluded:
141 if excluded:
142 for n in excluded:
142 for n in excluded:
143 ctx = repo[n]
143 ctx = repo[n]
144 if ctx.phase() >= phases.secret and not ctx.extinct():
144 if ctx.phase() >= phases.secret and not ctx.extinct():
145 secretlist.append(n)
145 secretlist.append(n)
146
146
147 if secretlist:
147 if secretlist:
148 ui.status(_("no changes found (ignored %d secret changesets)\n")
148 ui.status(_("no changes found (ignored %d secret changesets)\n")
149 % len(secretlist))
149 % len(secretlist))
150 else:
150 else:
151 ui.status(_("no changes found\n"))
151 ui.status(_("no changes found\n"))
152
152
153 def callcatch(ui, func):
153 def callcatch(ui, func):
154 """call func() with global exception handling
154 """call func() with global exception handling
155
155
156 return func() if no exception happens. otherwise do some error handling
156 return func() if no exception happens. otherwise do some error handling
157 and return an exit code accordingly. does not handle all exceptions.
157 and return an exit code accordingly. does not handle all exceptions.
158 """
158 """
159 try:
159 try:
160 try:
160 try:
161 return func()
161 return func()
162 except: # re-raises
162 except: # re-raises
163 ui.traceback()
163 ui.traceback()
164 raise
164 raise
165 # Global exception handling, alphabetically
165 # Global exception handling, alphabetically
166 # Mercurial-specific first, followed by built-in and library exceptions
166 # Mercurial-specific first, followed by built-in and library exceptions
167 except error.LockHeld as inst:
167 except error.LockHeld as inst:
168 if inst.errno == errno.ETIMEDOUT:
168 if inst.errno == errno.ETIMEDOUT:
169 reason = _('timed out waiting for lock held by %r') % inst.locker
169 reason = _('timed out waiting for lock held by %r') % inst.locker
170 else:
170 else:
171 reason = _('lock held by %r') % inst.locker
171 reason = _('lock held by %r') % inst.locker
172 ui.warn(_("abort: %s: %s\n")
172 ui.error(_("abort: %s: %s\n") % (
173 % (inst.desc or stringutil.forcebytestr(inst.filename), reason))
173 inst.desc or stringutil.forcebytestr(inst.filename), reason))
174 if not inst.locker:
174 if not inst.locker:
175 ui.warn(_("(lock might be very busy)\n"))
175 ui.error(_("(lock might be very busy)\n"))
176 except error.LockUnavailable as inst:
176 except error.LockUnavailable as inst:
177 ui.warn(_("abort: could not lock %s: %s\n") %
177 ui.error(_("abort: could not lock %s: %s\n") %
178 (inst.desc or stringutil.forcebytestr(inst.filename),
178 (inst.desc or stringutil.forcebytestr(inst.filename),
179 encoding.strtolocal(inst.strerror)))
179 encoding.strtolocal(inst.strerror)))
180 except error.OutOfBandError as inst:
180 except error.OutOfBandError as inst:
181 if inst.args:
181 if inst.args:
182 msg = _("abort: remote error:\n")
182 msg = _("abort: remote error:\n")
183 else:
183 else:
184 msg = _("abort: remote error\n")
184 msg = _("abort: remote error\n")
185 ui.warn(msg)
185 ui.error(msg)
186 if inst.args:
186 if inst.args:
187 ui.warn(''.join(inst.args))
187 ui.error(''.join(inst.args))
188 if inst.hint:
188 if inst.hint:
189 ui.warn('(%s)\n' % inst.hint)
189 ui.error('(%s)\n' % inst.hint)
190 except error.RepoError as inst:
190 except error.RepoError as inst:
191 ui.warn(_("abort: %s!\n") % inst)
191 ui.error(_("abort: %s!\n") % inst)
192 if inst.hint:
192 if inst.hint:
193 ui.warn(_("(%s)\n") % inst.hint)
193 ui.error(_("(%s)\n") % inst.hint)
194 except error.ResponseError as inst:
194 except error.ResponseError as inst:
195 ui.warn(_("abort: %s") % inst.args[0])
195 ui.error(_("abort: %s") % inst.args[0])
196 msg = inst.args[1]
196 msg = inst.args[1]
197 if isinstance(msg, type(u'')):
197 if isinstance(msg, type(u'')):
198 msg = pycompat.sysbytes(msg)
198 msg = pycompat.sysbytes(msg)
199 if not isinstance(msg, bytes):
199 if not isinstance(msg, bytes):
200 ui.warn(" %r\n" % (msg,))
200 ui.error(" %r\n" % (msg,))
201 elif not msg:
201 elif not msg:
202 ui.warn(_(" empty string\n"))
202 ui.error(_(" empty string\n"))
203 else:
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 except error.CensoredNodeError as inst:
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 except error.RevlogError as inst:
207 except error.RevlogError as inst:
208 ui.warn(_("abort: %s!\n") % inst)
208 ui.error(_("abort: %s!\n") % inst)
209 except error.InterventionRequired as inst:
209 except error.InterventionRequired as inst:
210 ui.warn("%s\n" % inst)
210 ui.error("%s\n" % inst)
211 if inst.hint:
211 if inst.hint:
212 ui.warn(_("(%s)\n") % inst.hint)
212 ui.error(_("(%s)\n") % inst.hint)
213 return 1
213 return 1
214 except error.WdirUnsupported:
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 except error.Abort as inst:
216 except error.Abort as inst:
217 ui.warn(_("abort: %s\n") % inst)
217 ui.error(_("abort: %s\n") % inst)
218 if inst.hint:
218 if inst.hint:
219 ui.warn(_("(%s)\n") % inst.hint)
219 ui.error(_("(%s)\n") % inst.hint)
220 except ImportError as inst:
220 except ImportError as inst:
221 ui.warn(_("abort: %s!\n") % stringutil.forcebytestr(inst))
221 ui.error(_("abort: %s!\n") % stringutil.forcebytestr(inst))
222 m = stringutil.forcebytestr(inst).split()[-1]
222 m = stringutil.forcebytestr(inst).split()[-1]
223 if m in "mpatch bdiff".split():
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 elif m in "zlib".split():
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 except IOError as inst:
227 except IOError as inst:
228 if util.safehasattr(inst, "code"):
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 elif util.safehasattr(inst, "reason"):
230 elif util.safehasattr(inst, "reason"):
231 try: # usually it is in the form (errno, strerror)
231 try: # usually it is in the form (errno, strerror)
232 reason = inst.reason.args[1]
232 reason = inst.reason.args[1]
233 except (AttributeError, IndexError):
233 except (AttributeError, IndexError):
234 # it might be anything, for example a string
234 # it might be anything, for example a string
235 reason = inst.reason
235 reason = inst.reason
236 if isinstance(reason, pycompat.unicode):
236 if isinstance(reason, pycompat.unicode):
237 # SSLError of Python 2.7.9 contains a unicode
237 # SSLError of Python 2.7.9 contains a unicode
238 reason = encoding.unitolocal(reason)
238 reason = encoding.unitolocal(reason)
239 ui.warn(_("abort: error: %s\n") % reason)
239 ui.error(_("abort: error: %s\n") % reason)
240 elif (util.safehasattr(inst, "args")
240 elif (util.safehasattr(inst, "args")
241 and inst.args and inst.args[0] == errno.EPIPE):
241 and inst.args and inst.args[0] == errno.EPIPE):
242 pass
242 pass
243 elif getattr(inst, "strerror", None):
243 elif getattr(inst, "strerror", None):
244 if getattr(inst, "filename", None):
244 if getattr(inst, "filename", None):
245 ui.warn(_("abort: %s: %s\n") % (
245 ui.error(_("abort: %s: %s\n") % (
246 encoding.strtolocal(inst.strerror),
246 encoding.strtolocal(inst.strerror),
247 stringutil.forcebytestr(inst.filename)))
247 stringutil.forcebytestr(inst.filename)))
248 else:
248 else:
249 ui.warn(_("abort: %s\n") % encoding.strtolocal(inst.strerror))
249 ui.error(_("abort: %s\n") % encoding.strtolocal(inst.strerror))
250 else:
250 else:
251 raise
251 raise
252 except OSError as inst:
252 except OSError as inst:
253 if getattr(inst, "filename", None) is not None:
253 if getattr(inst, "filename", None) is not None:
254 ui.warn(_("abort: %s: '%s'\n") % (
254 ui.error(_("abort: %s: '%s'\n") % (
255 encoding.strtolocal(inst.strerror),
255 encoding.strtolocal(inst.strerror),
256 stringutil.forcebytestr(inst.filename)))
256 stringutil.forcebytestr(inst.filename)))
257 else:
257 else:
258 ui.warn(_("abort: %s\n") % encoding.strtolocal(inst.strerror))
258 ui.error(_("abort: %s\n") % encoding.strtolocal(inst.strerror))
259 except MemoryError:
259 except MemoryError:
260 ui.warn(_("abort: out of memory\n"))
260 ui.error(_("abort: out of memory\n"))
261 except SystemExit as inst:
261 except SystemExit as inst:
262 # Commands shouldn't sys.exit directly, but give a return code.
262 # Commands shouldn't sys.exit directly, but give a return code.
263 # Just in case catch this and and pass exit code to caller.
263 # Just in case catch this and and pass exit code to caller.
264 return inst.code
264 return inst.code
265 except socket.error as inst:
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 return -1
268 return -1
269
269
270 def checknewlabel(repo, lbl, kind):
270 def checknewlabel(repo, lbl, kind):
271 # Do not use the "kind" parameter in ui output.
271 # Do not use the "kind" parameter in ui output.
272 # It makes strings difficult to translate.
272 # It makes strings difficult to translate.
273 if lbl in ['tip', '.', 'null']:
273 if lbl in ['tip', '.', 'null']:
274 raise error.Abort(_("the name '%s' is reserved") % lbl)
274 raise error.Abort(_("the name '%s' is reserved") % lbl)
275 for c in (':', '\0', '\n', '\r'):
275 for c in (':', '\0', '\n', '\r'):
276 if c in lbl:
276 if c in lbl:
277 raise error.Abort(
277 raise error.Abort(
278 _("%r cannot be used in a name") % pycompat.bytestr(c))
278 _("%r cannot be used in a name") % pycompat.bytestr(c))
279 try:
279 try:
280 int(lbl)
280 int(lbl)
281 raise error.Abort(_("cannot use an integer as a name"))
281 raise error.Abort(_("cannot use an integer as a name"))
282 except ValueError:
282 except ValueError:
283 pass
283 pass
284 if lbl.strip() != lbl:
284 if lbl.strip() != lbl:
285 raise error.Abort(_("leading or trailing whitespace in name %r") % lbl)
285 raise error.Abort(_("leading or trailing whitespace in name %r") % lbl)
286
286
287 def checkfilename(f):
287 def checkfilename(f):
288 '''Check that the filename f is an acceptable filename for a tracked file'''
288 '''Check that the filename f is an acceptable filename for a tracked file'''
289 if '\r' in f or '\n' in f:
289 if '\r' in f or '\n' in f:
290 raise error.Abort(_("'\\n' and '\\r' disallowed in filenames: %r")
290 raise error.Abort(_("'\\n' and '\\r' disallowed in filenames: %r")
291 % pycompat.bytestr(f))
291 % pycompat.bytestr(f))
292
292
293 def checkportable(ui, f):
293 def checkportable(ui, f):
294 '''Check if filename f is portable and warn or abort depending on config'''
294 '''Check if filename f is portable and warn or abort depending on config'''
295 checkfilename(f)
295 checkfilename(f)
296 abort, warn = checkportabilityalert(ui)
296 abort, warn = checkportabilityalert(ui)
297 if abort or warn:
297 if abort or warn:
298 msg = util.checkwinfilename(f)
298 msg = util.checkwinfilename(f)
299 if msg:
299 if msg:
300 msg = "%s: %s" % (msg, procutil.shellquote(f))
300 msg = "%s: %s" % (msg, procutil.shellquote(f))
301 if abort:
301 if abort:
302 raise error.Abort(msg)
302 raise error.Abort(msg)
303 ui.warn(_("warning: %s\n") % msg)
303 ui.warn(_("warning: %s\n") % msg)
304
304
305 def checkportabilityalert(ui):
305 def checkportabilityalert(ui):
306 '''check if the user's config requests nothing, a warning, or abort for
306 '''check if the user's config requests nothing, a warning, or abort for
307 non-portable filenames'''
307 non-portable filenames'''
308 val = ui.config('ui', 'portablefilenames')
308 val = ui.config('ui', 'portablefilenames')
309 lval = val.lower()
309 lval = val.lower()
310 bval = stringutil.parsebool(val)
310 bval = stringutil.parsebool(val)
311 abort = pycompat.iswindows or lval == 'abort'
311 abort = pycompat.iswindows or lval == 'abort'
312 warn = bval or lval == 'warn'
312 warn = bval or lval == 'warn'
313 if bval is None and not (warn or abort or lval == 'ignore'):
313 if bval is None and not (warn or abort or lval == 'ignore'):
314 raise error.ConfigError(
314 raise error.ConfigError(
315 _("ui.portablefilenames value is invalid ('%s')") % val)
315 _("ui.portablefilenames value is invalid ('%s')") % val)
316 return abort, warn
316 return abort, warn
317
317
318 class casecollisionauditor(object):
318 class casecollisionauditor(object):
319 def __init__(self, ui, abort, dirstate):
319 def __init__(self, ui, abort, dirstate):
320 self._ui = ui
320 self._ui = ui
321 self._abort = abort
321 self._abort = abort
322 allfiles = '\0'.join(dirstate._map)
322 allfiles = '\0'.join(dirstate._map)
323 self._loweredfiles = set(encoding.lower(allfiles).split('\0'))
323 self._loweredfiles = set(encoding.lower(allfiles).split('\0'))
324 self._dirstate = dirstate
324 self._dirstate = dirstate
325 # The purpose of _newfiles is so that we don't complain about
325 # The purpose of _newfiles is so that we don't complain about
326 # case collisions if someone were to call this object with the
326 # case collisions if someone were to call this object with the
327 # same filename twice.
327 # same filename twice.
328 self._newfiles = set()
328 self._newfiles = set()
329
329
330 def __call__(self, f):
330 def __call__(self, f):
331 if f in self._newfiles:
331 if f in self._newfiles:
332 return
332 return
333 fl = encoding.lower(f)
333 fl = encoding.lower(f)
334 if fl in self._loweredfiles and f not in self._dirstate:
334 if fl in self._loweredfiles and f not in self._dirstate:
335 msg = _('possible case-folding collision for %s') % f
335 msg = _('possible case-folding collision for %s') % f
336 if self._abort:
336 if self._abort:
337 raise error.Abort(msg)
337 raise error.Abort(msg)
338 self._ui.warn(_("warning: %s\n") % msg)
338 self._ui.warn(_("warning: %s\n") % msg)
339 self._loweredfiles.add(fl)
339 self._loweredfiles.add(fl)
340 self._newfiles.add(f)
340 self._newfiles.add(f)
341
341
342 def filteredhash(repo, maxrev):
342 def filteredhash(repo, maxrev):
343 """build hash of filtered revisions in the current repoview.
343 """build hash of filtered revisions in the current repoview.
344
344
345 Multiple caches perform up-to-date validation by checking that the
345 Multiple caches perform up-to-date validation by checking that the
346 tiprev and tipnode stored in the cache file match the current repository.
346 tiprev and tipnode stored in the cache file match the current repository.
347 However, this is not sufficient for validating repoviews because the set
347 However, this is not sufficient for validating repoviews because the set
348 of revisions in the view may change without the repository tiprev and
348 of revisions in the view may change without the repository tiprev and
349 tipnode changing.
349 tipnode changing.
350
350
351 This function hashes all the revs filtered from the view and returns
351 This function hashes all the revs filtered from the view and returns
352 that SHA-1 digest.
352 that SHA-1 digest.
353 """
353 """
354 cl = repo.changelog
354 cl = repo.changelog
355 if not cl.filteredrevs:
355 if not cl.filteredrevs:
356 return None
356 return None
357 key = None
357 key = None
358 revs = sorted(r for r in cl.filteredrevs if r <= maxrev)
358 revs = sorted(r for r in cl.filteredrevs if r <= maxrev)
359 if revs:
359 if revs:
360 s = hashlib.sha1()
360 s = hashlib.sha1()
361 for rev in revs:
361 for rev in revs:
362 s.update('%d;' % rev)
362 s.update('%d;' % rev)
363 key = s.digest()
363 key = s.digest()
364 return key
364 return key
365
365
366 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
366 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
367 '''yield every hg repository under path, always recursively.
367 '''yield every hg repository under path, always recursively.
368 The recurse flag will only control recursion into repo working dirs'''
368 The recurse flag will only control recursion into repo working dirs'''
369 def errhandler(err):
369 def errhandler(err):
370 if err.filename == path:
370 if err.filename == path:
371 raise err
371 raise err
372 samestat = getattr(os.path, 'samestat', None)
372 samestat = getattr(os.path, 'samestat', None)
373 if followsym and samestat is not None:
373 if followsym and samestat is not None:
374 def adddir(dirlst, dirname):
374 def adddir(dirlst, dirname):
375 dirstat = os.stat(dirname)
375 dirstat = os.stat(dirname)
376 match = any(samestat(dirstat, lstdirstat) for lstdirstat in dirlst)
376 match = any(samestat(dirstat, lstdirstat) for lstdirstat in dirlst)
377 if not match:
377 if not match:
378 dirlst.append(dirstat)
378 dirlst.append(dirstat)
379 return not match
379 return not match
380 else:
380 else:
381 followsym = False
381 followsym = False
382
382
383 if (seen_dirs is None) and followsym:
383 if (seen_dirs is None) and followsym:
384 seen_dirs = []
384 seen_dirs = []
385 adddir(seen_dirs, path)
385 adddir(seen_dirs, path)
386 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
386 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
387 dirs.sort()
387 dirs.sort()
388 if '.hg' in dirs:
388 if '.hg' in dirs:
389 yield root # found a repository
389 yield root # found a repository
390 qroot = os.path.join(root, '.hg', 'patches')
390 qroot = os.path.join(root, '.hg', 'patches')
391 if os.path.isdir(os.path.join(qroot, '.hg')):
391 if os.path.isdir(os.path.join(qroot, '.hg')):
392 yield qroot # we have a patch queue repo here
392 yield qroot # we have a patch queue repo here
393 if recurse:
393 if recurse:
394 # avoid recursing inside the .hg directory
394 # avoid recursing inside the .hg directory
395 dirs.remove('.hg')
395 dirs.remove('.hg')
396 else:
396 else:
397 dirs[:] = [] # don't descend further
397 dirs[:] = [] # don't descend further
398 elif followsym:
398 elif followsym:
399 newdirs = []
399 newdirs = []
400 for d in dirs:
400 for d in dirs:
401 fname = os.path.join(root, d)
401 fname = os.path.join(root, d)
402 if adddir(seen_dirs, fname):
402 if adddir(seen_dirs, fname):
403 if os.path.islink(fname):
403 if os.path.islink(fname):
404 for hgname in walkrepos(fname, True, seen_dirs):
404 for hgname in walkrepos(fname, True, seen_dirs):
405 yield hgname
405 yield hgname
406 else:
406 else:
407 newdirs.append(d)
407 newdirs.append(d)
408 dirs[:] = newdirs
408 dirs[:] = newdirs
409
409
410 def binnode(ctx):
410 def binnode(ctx):
411 """Return binary node id for a given basectx"""
411 """Return binary node id for a given basectx"""
412 node = ctx.node()
412 node = ctx.node()
413 if node is None:
413 if node is None:
414 return wdirid
414 return wdirid
415 return node
415 return node
416
416
417 def intrev(ctx):
417 def intrev(ctx):
418 """Return integer for a given basectx that can be used in comparison or
418 """Return integer for a given basectx that can be used in comparison or
419 arithmetic operation"""
419 arithmetic operation"""
420 rev = ctx.rev()
420 rev = ctx.rev()
421 if rev is None:
421 if rev is None:
422 return wdirrev
422 return wdirrev
423 return rev
423 return rev
424
424
425 def formatchangeid(ctx):
425 def formatchangeid(ctx):
426 """Format changectx as '{rev}:{node|formatnode}', which is the default
426 """Format changectx as '{rev}:{node|formatnode}', which is the default
427 template provided by logcmdutil.changesettemplater"""
427 template provided by logcmdutil.changesettemplater"""
428 repo = ctx.repo()
428 repo = ctx.repo()
429 return formatrevnode(repo.ui, intrev(ctx), binnode(ctx))
429 return formatrevnode(repo.ui, intrev(ctx), binnode(ctx))
430
430
431 def formatrevnode(ui, rev, node):
431 def formatrevnode(ui, rev, node):
432 """Format given revision and node depending on the current verbosity"""
432 """Format given revision and node depending on the current verbosity"""
433 if ui.debugflag:
433 if ui.debugflag:
434 hexfunc = hex
434 hexfunc = hex
435 else:
435 else:
436 hexfunc = short
436 hexfunc = short
437 return '%d:%s' % (rev, hexfunc(node))
437 return '%d:%s' % (rev, hexfunc(node))
438
438
439 def resolvehexnodeidprefix(repo, prefix):
439 def resolvehexnodeidprefix(repo, prefix):
440 # Uses unfiltered repo because it's faster when prefix is ambiguous/
440 # Uses unfiltered repo because it's faster when prefix is ambiguous/
441 # This matches the shortesthexnodeidprefix() function below.
441 # This matches the shortesthexnodeidprefix() function below.
442 node = repo.unfiltered().changelog._partialmatch(prefix)
442 node = repo.unfiltered().changelog._partialmatch(prefix)
443 if node is None:
443 if node is None:
444 return
444 return
445 repo.changelog.rev(node) # make sure node isn't filtered
445 repo.changelog.rev(node) # make sure node isn't filtered
446 return node
446 return node
447
447
448 def shortesthexnodeidprefix(repo, node, minlength=1):
448 def shortesthexnodeidprefix(repo, node, minlength=1):
449 """Find the shortest unambiguous prefix that matches hexnode."""
449 """Find the shortest unambiguous prefix that matches hexnode."""
450 # _partialmatch() of filtered changelog could take O(len(repo)) time,
450 # _partialmatch() of filtered changelog could take O(len(repo)) time,
451 # which would be unacceptably slow. so we look for hash collision in
451 # which would be unacceptably slow. so we look for hash collision in
452 # unfiltered space, which means some hashes may be slightly longer.
452 # unfiltered space, which means some hashes may be slightly longer.
453 cl = repo.unfiltered().changelog
453 cl = repo.unfiltered().changelog
454
454
455 def isrev(prefix):
455 def isrev(prefix):
456 try:
456 try:
457 i = int(prefix)
457 i = int(prefix)
458 # if we are a pure int, then starting with zero will not be
458 # if we are a pure int, then starting with zero will not be
459 # confused as a rev; or, obviously, if the int is larger
459 # confused as a rev; or, obviously, if the int is larger
460 # than the value of the tip rev
460 # than the value of the tip rev
461 if prefix[0:1] == b'0' or i > len(cl):
461 if prefix[0:1] == b'0' or i > len(cl):
462 return False
462 return False
463 return True
463 return True
464 except ValueError:
464 except ValueError:
465 return False
465 return False
466
466
467 def disambiguate(prefix):
467 def disambiguate(prefix):
468 """Disambiguate against revnums."""
468 """Disambiguate against revnums."""
469 hexnode = hex(node)
469 hexnode = hex(node)
470 for length in range(len(prefix), len(hexnode) + 1):
470 for length in range(len(prefix), len(hexnode) + 1):
471 prefix = hexnode[:length]
471 prefix = hexnode[:length]
472 if not isrev(prefix):
472 if not isrev(prefix):
473 return prefix
473 return prefix
474
474
475 try:
475 try:
476 return disambiguate(cl.shortest(node, minlength))
476 return disambiguate(cl.shortest(node, minlength))
477 except error.LookupError:
477 except error.LookupError:
478 raise error.RepoLookupError()
478 raise error.RepoLookupError()
479
479
480 def isrevsymbol(repo, symbol):
480 def isrevsymbol(repo, symbol):
481 """Checks if a symbol exists in the repo.
481 """Checks if a symbol exists in the repo.
482
482
483 See revsymbol() for details. Raises error.LookupError if the symbol is an
483 See revsymbol() for details. Raises error.LookupError if the symbol is an
484 ambiguous nodeid prefix.
484 ambiguous nodeid prefix.
485 """
485 """
486 try:
486 try:
487 revsymbol(repo, symbol)
487 revsymbol(repo, symbol)
488 return True
488 return True
489 except error.RepoLookupError:
489 except error.RepoLookupError:
490 return False
490 return False
491
491
492 def revsymbol(repo, symbol):
492 def revsymbol(repo, symbol):
493 """Returns a context given a single revision symbol (as string).
493 """Returns a context given a single revision symbol (as string).
494
494
495 This is similar to revsingle(), but accepts only a single revision symbol,
495 This is similar to revsingle(), but accepts only a single revision symbol,
496 i.e. things like ".", "tip", "1234", "deadbeef", "my-bookmark" work, but
496 i.e. things like ".", "tip", "1234", "deadbeef", "my-bookmark" work, but
497 not "max(public())".
497 not "max(public())".
498 """
498 """
499 if not isinstance(symbol, bytes):
499 if not isinstance(symbol, bytes):
500 msg = ("symbol (%s of type %s) was not a string, did you mean "
500 msg = ("symbol (%s of type %s) was not a string, did you mean "
501 "repo[symbol]?" % (symbol, type(symbol)))
501 "repo[symbol]?" % (symbol, type(symbol)))
502 raise error.ProgrammingError(msg)
502 raise error.ProgrammingError(msg)
503 try:
503 try:
504 if symbol in ('.', 'tip', 'null'):
504 if symbol in ('.', 'tip', 'null'):
505 return repo[symbol]
505 return repo[symbol]
506
506
507 try:
507 try:
508 r = int(symbol)
508 r = int(symbol)
509 if '%d' % r != symbol:
509 if '%d' % r != symbol:
510 raise ValueError
510 raise ValueError
511 l = len(repo.changelog)
511 l = len(repo.changelog)
512 if r < 0:
512 if r < 0:
513 r += l
513 r += l
514 if r < 0 or r >= l and r != wdirrev:
514 if r < 0 or r >= l and r != wdirrev:
515 raise ValueError
515 raise ValueError
516 return repo[r]
516 return repo[r]
517 except error.FilteredIndexError:
517 except error.FilteredIndexError:
518 raise
518 raise
519 except (ValueError, OverflowError, IndexError):
519 except (ValueError, OverflowError, IndexError):
520 pass
520 pass
521
521
522 if len(symbol) == 40:
522 if len(symbol) == 40:
523 try:
523 try:
524 node = bin(symbol)
524 node = bin(symbol)
525 rev = repo.changelog.rev(node)
525 rev = repo.changelog.rev(node)
526 return repo[rev]
526 return repo[rev]
527 except error.FilteredLookupError:
527 except error.FilteredLookupError:
528 raise
528 raise
529 except (TypeError, LookupError):
529 except (TypeError, LookupError):
530 pass
530 pass
531
531
532 # look up bookmarks through the name interface
532 # look up bookmarks through the name interface
533 try:
533 try:
534 node = repo.names.singlenode(repo, symbol)
534 node = repo.names.singlenode(repo, symbol)
535 rev = repo.changelog.rev(node)
535 rev = repo.changelog.rev(node)
536 return repo[rev]
536 return repo[rev]
537 except KeyError:
537 except KeyError:
538 pass
538 pass
539
539
540 node = resolvehexnodeidprefix(repo, symbol)
540 node = resolvehexnodeidprefix(repo, symbol)
541 if node is not None:
541 if node is not None:
542 rev = repo.changelog.rev(node)
542 rev = repo.changelog.rev(node)
543 return repo[rev]
543 return repo[rev]
544
544
545 raise error.RepoLookupError(_("unknown revision '%s'") % symbol)
545 raise error.RepoLookupError(_("unknown revision '%s'") % symbol)
546
546
547 except error.WdirUnsupported:
547 except error.WdirUnsupported:
548 return repo[None]
548 return repo[None]
549 except (error.FilteredIndexError, error.FilteredLookupError,
549 except (error.FilteredIndexError, error.FilteredLookupError,
550 error.FilteredRepoLookupError):
550 error.FilteredRepoLookupError):
551 raise _filterederror(repo, symbol)
551 raise _filterederror(repo, symbol)
552
552
553 def _filterederror(repo, changeid):
553 def _filterederror(repo, changeid):
554 """build an exception to be raised about a filtered changeid
554 """build an exception to be raised about a filtered changeid
555
555
556 This is extracted in a function to help extensions (eg: evolve) to
556 This is extracted in a function to help extensions (eg: evolve) to
557 experiment with various message variants."""
557 experiment with various message variants."""
558 if repo.filtername.startswith('visible'):
558 if repo.filtername.startswith('visible'):
559
559
560 # Check if the changeset is obsolete
560 # Check if the changeset is obsolete
561 unfilteredrepo = repo.unfiltered()
561 unfilteredrepo = repo.unfiltered()
562 ctx = revsymbol(unfilteredrepo, changeid)
562 ctx = revsymbol(unfilteredrepo, changeid)
563
563
564 # If the changeset is obsolete, enrich the message with the reason
564 # If the changeset is obsolete, enrich the message with the reason
565 # that made this changeset not visible
565 # that made this changeset not visible
566 if ctx.obsolete():
566 if ctx.obsolete():
567 msg = obsutil._getfilteredreason(repo, changeid, ctx)
567 msg = obsutil._getfilteredreason(repo, changeid, ctx)
568 else:
568 else:
569 msg = _("hidden revision '%s'") % changeid
569 msg = _("hidden revision '%s'") % changeid
570
570
571 hint = _('use --hidden to access hidden revisions')
571 hint = _('use --hidden to access hidden revisions')
572
572
573 return error.FilteredRepoLookupError(msg, hint=hint)
573 return error.FilteredRepoLookupError(msg, hint=hint)
574 msg = _("filtered revision '%s' (not in '%s' subset)")
574 msg = _("filtered revision '%s' (not in '%s' subset)")
575 msg %= (changeid, repo.filtername)
575 msg %= (changeid, repo.filtername)
576 return error.FilteredRepoLookupError(msg)
576 return error.FilteredRepoLookupError(msg)
577
577
578 def revsingle(repo, revspec, default='.', localalias=None):
578 def revsingle(repo, revspec, default='.', localalias=None):
579 if not revspec and revspec != 0:
579 if not revspec and revspec != 0:
580 return repo[default]
580 return repo[default]
581
581
582 l = revrange(repo, [revspec], localalias=localalias)
582 l = revrange(repo, [revspec], localalias=localalias)
583 if not l:
583 if not l:
584 raise error.Abort(_('empty revision set'))
584 raise error.Abort(_('empty revision set'))
585 return repo[l.last()]
585 return repo[l.last()]
586
586
587 def _pairspec(revspec):
587 def _pairspec(revspec):
588 tree = revsetlang.parse(revspec)
588 tree = revsetlang.parse(revspec)
589 return tree and tree[0] in ('range', 'rangepre', 'rangepost', 'rangeall')
589 return tree and tree[0] in ('range', 'rangepre', 'rangepost', 'rangeall')
590
590
591 def revpair(repo, revs):
591 def revpair(repo, revs):
592 if not revs:
592 if not revs:
593 return repo['.'], repo[None]
593 return repo['.'], repo[None]
594
594
595 l = revrange(repo, revs)
595 l = revrange(repo, revs)
596
596
597 if not l:
597 if not l:
598 first = second = None
598 first = second = None
599 elif l.isascending():
599 elif l.isascending():
600 first = l.min()
600 first = l.min()
601 second = l.max()
601 second = l.max()
602 elif l.isdescending():
602 elif l.isdescending():
603 first = l.max()
603 first = l.max()
604 second = l.min()
604 second = l.min()
605 else:
605 else:
606 first = l.first()
606 first = l.first()
607 second = l.last()
607 second = l.last()
608
608
609 if first is None:
609 if first is None:
610 raise error.Abort(_('empty revision range'))
610 raise error.Abort(_('empty revision range'))
611 if (first == second and len(revs) >= 2
611 if (first == second and len(revs) >= 2
612 and not all(revrange(repo, [r]) for r in revs)):
612 and not all(revrange(repo, [r]) for r in revs)):
613 raise error.Abort(_('empty revision on one side of range'))
613 raise error.Abort(_('empty revision on one side of range'))
614
614
615 # if top-level is range expression, the result must always be a pair
615 # if top-level is range expression, the result must always be a pair
616 if first == second and len(revs) == 1 and not _pairspec(revs[0]):
616 if first == second and len(revs) == 1 and not _pairspec(revs[0]):
617 return repo[first], repo[None]
617 return repo[first], repo[None]
618
618
619 return repo[first], repo[second]
619 return repo[first], repo[second]
620
620
621 def revrange(repo, specs, localalias=None):
621 def revrange(repo, specs, localalias=None):
622 """Execute 1 to many revsets and return the union.
622 """Execute 1 to many revsets and return the union.
623
623
624 This is the preferred mechanism for executing revsets using user-specified
624 This is the preferred mechanism for executing revsets using user-specified
625 config options, such as revset aliases.
625 config options, such as revset aliases.
626
626
627 The revsets specified by ``specs`` will be executed via a chained ``OR``
627 The revsets specified by ``specs`` will be executed via a chained ``OR``
628 expression. If ``specs`` is empty, an empty result is returned.
628 expression. If ``specs`` is empty, an empty result is returned.
629
629
630 ``specs`` can contain integers, in which case they are assumed to be
630 ``specs`` can contain integers, in which case they are assumed to be
631 revision numbers.
631 revision numbers.
632
632
633 It is assumed the revsets are already formatted. If you have arguments
633 It is assumed the revsets are already formatted. If you have arguments
634 that need to be expanded in the revset, call ``revsetlang.formatspec()``
634 that need to be expanded in the revset, call ``revsetlang.formatspec()``
635 and pass the result as an element of ``specs``.
635 and pass the result as an element of ``specs``.
636
636
637 Specifying a single revset is allowed.
637 Specifying a single revset is allowed.
638
638
639 Returns a ``revset.abstractsmartset`` which is a list-like interface over
639 Returns a ``revset.abstractsmartset`` which is a list-like interface over
640 integer revisions.
640 integer revisions.
641 """
641 """
642 allspecs = []
642 allspecs = []
643 for spec in specs:
643 for spec in specs:
644 if isinstance(spec, int):
644 if isinstance(spec, int):
645 spec = revsetlang.formatspec('rev(%d)', spec)
645 spec = revsetlang.formatspec('rev(%d)', spec)
646 allspecs.append(spec)
646 allspecs.append(spec)
647 return repo.anyrevs(allspecs, user=True, localalias=localalias)
647 return repo.anyrevs(allspecs, user=True, localalias=localalias)
648
648
649 def meaningfulparents(repo, ctx):
649 def meaningfulparents(repo, ctx):
650 """Return list of meaningful (or all if debug) parentrevs for rev.
650 """Return list of meaningful (or all if debug) parentrevs for rev.
651
651
652 For merges (two non-nullrev revisions) both parents are meaningful.
652 For merges (two non-nullrev revisions) both parents are meaningful.
653 Otherwise the first parent revision is considered meaningful if it
653 Otherwise the first parent revision is considered meaningful if it
654 is not the preceding revision.
654 is not the preceding revision.
655 """
655 """
656 parents = ctx.parents()
656 parents = ctx.parents()
657 if len(parents) > 1:
657 if len(parents) > 1:
658 return parents
658 return parents
659 if repo.ui.debugflag:
659 if repo.ui.debugflag:
660 return [parents[0], repo['null']]
660 return [parents[0], repo['null']]
661 if parents[0].rev() >= intrev(ctx) - 1:
661 if parents[0].rev() >= intrev(ctx) - 1:
662 return []
662 return []
663 return parents
663 return parents
664
664
665 def expandpats(pats):
665 def expandpats(pats):
666 '''Expand bare globs when running on windows.
666 '''Expand bare globs when running on windows.
667 On posix we assume it already has already been done by sh.'''
667 On posix we assume it already has already been done by sh.'''
668 if not util.expandglobs:
668 if not util.expandglobs:
669 return list(pats)
669 return list(pats)
670 ret = []
670 ret = []
671 for kindpat in pats:
671 for kindpat in pats:
672 kind, pat = matchmod._patsplit(kindpat, None)
672 kind, pat = matchmod._patsplit(kindpat, None)
673 if kind is None:
673 if kind is None:
674 try:
674 try:
675 globbed = glob.glob(pat)
675 globbed = glob.glob(pat)
676 except re.error:
676 except re.error:
677 globbed = [pat]
677 globbed = [pat]
678 if globbed:
678 if globbed:
679 ret.extend(globbed)
679 ret.extend(globbed)
680 continue
680 continue
681 ret.append(kindpat)
681 ret.append(kindpat)
682 return ret
682 return ret
683
683
684 def matchandpats(ctx, pats=(), opts=None, globbed=False, default='relpath',
684 def matchandpats(ctx, pats=(), opts=None, globbed=False, default='relpath',
685 badfn=None):
685 badfn=None):
686 '''Return a matcher and the patterns that were used.
686 '''Return a matcher and the patterns that were used.
687 The matcher will warn about bad matches, unless an alternate badfn callback
687 The matcher will warn about bad matches, unless an alternate badfn callback
688 is provided.'''
688 is provided.'''
689 if pats == ("",):
689 if pats == ("",):
690 pats = []
690 pats = []
691 if opts is None:
691 if opts is None:
692 opts = {}
692 opts = {}
693 if not globbed and default == 'relpath':
693 if not globbed and default == 'relpath':
694 pats = expandpats(pats or [])
694 pats = expandpats(pats or [])
695
695
696 def bad(f, msg):
696 def bad(f, msg):
697 ctx.repo().ui.warn("%s: %s\n" % (m.rel(f), msg))
697 ctx.repo().ui.warn("%s: %s\n" % (m.rel(f), msg))
698
698
699 if badfn is None:
699 if badfn is None:
700 badfn = bad
700 badfn = bad
701
701
702 m = ctx.match(pats, opts.get('include'), opts.get('exclude'),
702 m = ctx.match(pats, opts.get('include'), opts.get('exclude'),
703 default, listsubrepos=opts.get('subrepos'), badfn=badfn)
703 default, listsubrepos=opts.get('subrepos'), badfn=badfn)
704
704
705 if m.always():
705 if m.always():
706 pats = []
706 pats = []
707 return m, pats
707 return m, pats
708
708
709 def match(ctx, pats=(), opts=None, globbed=False, default='relpath',
709 def match(ctx, pats=(), opts=None, globbed=False, default='relpath',
710 badfn=None):
710 badfn=None):
711 '''Return a matcher that will warn about bad matches.'''
711 '''Return a matcher that will warn about bad matches.'''
712 return matchandpats(ctx, pats, opts, globbed, default, badfn=badfn)[0]
712 return matchandpats(ctx, pats, opts, globbed, default, badfn=badfn)[0]
713
713
714 def matchall(repo):
714 def matchall(repo):
715 '''Return a matcher that will efficiently match everything.'''
715 '''Return a matcher that will efficiently match everything.'''
716 return matchmod.always(repo.root, repo.getcwd())
716 return matchmod.always(repo.root, repo.getcwd())
717
717
718 def matchfiles(repo, files, badfn=None):
718 def matchfiles(repo, files, badfn=None):
719 '''Return a matcher that will efficiently match exactly these files.'''
719 '''Return a matcher that will efficiently match exactly these files.'''
720 return matchmod.exact(repo.root, repo.getcwd(), files, badfn=badfn)
720 return matchmod.exact(repo.root, repo.getcwd(), files, badfn=badfn)
721
721
722 def parsefollowlinespattern(repo, rev, pat, msg):
722 def parsefollowlinespattern(repo, rev, pat, msg):
723 """Return a file name from `pat` pattern suitable for usage in followlines
723 """Return a file name from `pat` pattern suitable for usage in followlines
724 logic.
724 logic.
725 """
725 """
726 if not matchmod.patkind(pat):
726 if not matchmod.patkind(pat):
727 return pathutil.canonpath(repo.root, repo.getcwd(), pat)
727 return pathutil.canonpath(repo.root, repo.getcwd(), pat)
728 else:
728 else:
729 ctx = repo[rev]
729 ctx = repo[rev]
730 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=ctx)
730 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=ctx)
731 files = [f for f in ctx if m(f)]
731 files = [f for f in ctx if m(f)]
732 if len(files) != 1:
732 if len(files) != 1:
733 raise error.ParseError(msg)
733 raise error.ParseError(msg)
734 return files[0]
734 return files[0]
735
735
736 def origpath(ui, repo, filepath):
736 def origpath(ui, repo, filepath):
737 '''customize where .orig files are created
737 '''customize where .orig files are created
738
738
739 Fetch user defined path from config file: [ui] origbackuppath = <path>
739 Fetch user defined path from config file: [ui] origbackuppath = <path>
740 Fall back to default (filepath with .orig suffix) if not specified
740 Fall back to default (filepath with .orig suffix) if not specified
741 '''
741 '''
742 origbackuppath = ui.config('ui', 'origbackuppath')
742 origbackuppath = ui.config('ui', 'origbackuppath')
743 if not origbackuppath:
743 if not origbackuppath:
744 return filepath + ".orig"
744 return filepath + ".orig"
745
745
746 # Convert filepath from an absolute path into a path inside the repo.
746 # Convert filepath from an absolute path into a path inside the repo.
747 filepathfromroot = util.normpath(os.path.relpath(filepath,
747 filepathfromroot = util.normpath(os.path.relpath(filepath,
748 start=repo.root))
748 start=repo.root))
749
749
750 origvfs = vfs.vfs(repo.wjoin(origbackuppath))
750 origvfs = vfs.vfs(repo.wjoin(origbackuppath))
751 origbackupdir = origvfs.dirname(filepathfromroot)
751 origbackupdir = origvfs.dirname(filepathfromroot)
752 if not origvfs.isdir(origbackupdir) or origvfs.islink(origbackupdir):
752 if not origvfs.isdir(origbackupdir) or origvfs.islink(origbackupdir):
753 ui.note(_('creating directory: %s\n') % origvfs.join(origbackupdir))
753 ui.note(_('creating directory: %s\n') % origvfs.join(origbackupdir))
754
754
755 # Remove any files that conflict with the backup file's path
755 # Remove any files that conflict with the backup file's path
756 for f in reversed(list(util.finddirs(filepathfromroot))):
756 for f in reversed(list(util.finddirs(filepathfromroot))):
757 if origvfs.isfileorlink(f):
757 if origvfs.isfileorlink(f):
758 ui.note(_('removing conflicting file: %s\n')
758 ui.note(_('removing conflicting file: %s\n')
759 % origvfs.join(f))
759 % origvfs.join(f))
760 origvfs.unlink(f)
760 origvfs.unlink(f)
761 break
761 break
762
762
763 origvfs.makedirs(origbackupdir)
763 origvfs.makedirs(origbackupdir)
764
764
765 if origvfs.isdir(filepathfromroot) and not origvfs.islink(filepathfromroot):
765 if origvfs.isdir(filepathfromroot) and not origvfs.islink(filepathfromroot):
766 ui.note(_('removing conflicting directory: %s\n')
766 ui.note(_('removing conflicting directory: %s\n')
767 % origvfs.join(filepathfromroot))
767 % origvfs.join(filepathfromroot))
768 origvfs.rmtree(filepathfromroot, forcibly=True)
768 origvfs.rmtree(filepathfromroot, forcibly=True)
769
769
770 return origvfs.join(filepathfromroot)
770 return origvfs.join(filepathfromroot)
771
771
772 class _containsnode(object):
772 class _containsnode(object):
773 """proxy __contains__(node) to container.__contains__ which accepts revs"""
773 """proxy __contains__(node) to container.__contains__ which accepts revs"""
774
774
775 def __init__(self, repo, revcontainer):
775 def __init__(self, repo, revcontainer):
776 self._torev = repo.changelog.rev
776 self._torev = repo.changelog.rev
777 self._revcontains = revcontainer.__contains__
777 self._revcontains = revcontainer.__contains__
778
778
779 def __contains__(self, node):
779 def __contains__(self, node):
780 return self._revcontains(self._torev(node))
780 return self._revcontains(self._torev(node))
781
781
782 def cleanupnodes(repo, replacements, operation, moves=None, metadata=None,
782 def cleanupnodes(repo, replacements, operation, moves=None, metadata=None,
783 fixphase=False, targetphase=None):
783 fixphase=False, targetphase=None):
784 """do common cleanups when old nodes are replaced by new nodes
784 """do common cleanups when old nodes are replaced by new nodes
785
785
786 That includes writing obsmarkers or stripping nodes, and moving bookmarks.
786 That includes writing obsmarkers or stripping nodes, and moving bookmarks.
787 (we might also want to move working directory parent in the future)
787 (we might also want to move working directory parent in the future)
788
788
789 By default, bookmark moves are calculated automatically from 'replacements',
789 By default, bookmark moves are calculated automatically from 'replacements',
790 but 'moves' can be used to override that. Also, 'moves' may include
790 but 'moves' can be used to override that. Also, 'moves' may include
791 additional bookmark moves that should not have associated obsmarkers.
791 additional bookmark moves that should not have associated obsmarkers.
792
792
793 replacements is {oldnode: [newnode]} or a iterable of nodes if they do not
793 replacements is {oldnode: [newnode]} or a iterable of nodes if they do not
794 have replacements. operation is a string, like "rebase".
794 have replacements. operation is a string, like "rebase".
795
795
796 metadata is dictionary containing metadata to be stored in obsmarker if
796 metadata is dictionary containing metadata to be stored in obsmarker if
797 obsolescence is enabled.
797 obsolescence is enabled.
798 """
798 """
799 assert fixphase or targetphase is None
799 assert fixphase or targetphase is None
800 if not replacements and not moves:
800 if not replacements and not moves:
801 return
801 return
802
802
803 # translate mapping's other forms
803 # translate mapping's other forms
804 if not util.safehasattr(replacements, 'items'):
804 if not util.safehasattr(replacements, 'items'):
805 replacements = {n: () for n in replacements}
805 replacements = {n: () for n in replacements}
806
806
807 # Calculate bookmark movements
807 # Calculate bookmark movements
808 if moves is None:
808 if moves is None:
809 moves = {}
809 moves = {}
810 # Unfiltered repo is needed since nodes in replacements might be hidden.
810 # Unfiltered repo is needed since nodes in replacements might be hidden.
811 unfi = repo.unfiltered()
811 unfi = repo.unfiltered()
812 for oldnode, newnodes in replacements.items():
812 for oldnode, newnodes in replacements.items():
813 if oldnode in moves:
813 if oldnode in moves:
814 continue
814 continue
815 if len(newnodes) > 1:
815 if len(newnodes) > 1:
816 # usually a split, take the one with biggest rev number
816 # usually a split, take the one with biggest rev number
817 newnode = next(unfi.set('max(%ln)', newnodes)).node()
817 newnode = next(unfi.set('max(%ln)', newnodes)).node()
818 elif len(newnodes) == 0:
818 elif len(newnodes) == 0:
819 # move bookmark backwards
819 # move bookmark backwards
820 roots = list(unfi.set('max((::%n) - %ln)', oldnode,
820 roots = list(unfi.set('max((::%n) - %ln)', oldnode,
821 list(replacements)))
821 list(replacements)))
822 if roots:
822 if roots:
823 newnode = roots[0].node()
823 newnode = roots[0].node()
824 else:
824 else:
825 newnode = nullid
825 newnode = nullid
826 else:
826 else:
827 newnode = newnodes[0]
827 newnode = newnodes[0]
828 moves[oldnode] = newnode
828 moves[oldnode] = newnode
829
829
830 allnewnodes = [n for ns in replacements.values() for n in ns]
830 allnewnodes = [n for ns in replacements.values() for n in ns]
831 toretract = {}
831 toretract = {}
832 toadvance = {}
832 toadvance = {}
833 if fixphase:
833 if fixphase:
834 precursors = {}
834 precursors = {}
835 for oldnode, newnodes in replacements.items():
835 for oldnode, newnodes in replacements.items():
836 for newnode in newnodes:
836 for newnode in newnodes:
837 precursors.setdefault(newnode, []).append(oldnode)
837 precursors.setdefault(newnode, []).append(oldnode)
838
838
839 allnewnodes.sort(key=lambda n: unfi[n].rev())
839 allnewnodes.sort(key=lambda n: unfi[n].rev())
840 newphases = {}
840 newphases = {}
841 def phase(ctx):
841 def phase(ctx):
842 return newphases.get(ctx.node(), ctx.phase())
842 return newphases.get(ctx.node(), ctx.phase())
843 for newnode in allnewnodes:
843 for newnode in allnewnodes:
844 ctx = unfi[newnode]
844 ctx = unfi[newnode]
845 parentphase = max(phase(p) for p in ctx.parents())
845 parentphase = max(phase(p) for p in ctx.parents())
846 if targetphase is None:
846 if targetphase is None:
847 oldphase = max(unfi[oldnode].phase()
847 oldphase = max(unfi[oldnode].phase()
848 for oldnode in precursors[newnode])
848 for oldnode in precursors[newnode])
849 newphase = max(oldphase, parentphase)
849 newphase = max(oldphase, parentphase)
850 else:
850 else:
851 newphase = max(targetphase, parentphase)
851 newphase = max(targetphase, parentphase)
852 newphases[newnode] = newphase
852 newphases[newnode] = newphase
853 if newphase > ctx.phase():
853 if newphase > ctx.phase():
854 toretract.setdefault(newphase, []).append(newnode)
854 toretract.setdefault(newphase, []).append(newnode)
855 elif newphase < ctx.phase():
855 elif newphase < ctx.phase():
856 toadvance.setdefault(newphase, []).append(newnode)
856 toadvance.setdefault(newphase, []).append(newnode)
857
857
858 with repo.transaction('cleanup') as tr:
858 with repo.transaction('cleanup') as tr:
859 # Move bookmarks
859 # Move bookmarks
860 bmarks = repo._bookmarks
860 bmarks = repo._bookmarks
861 bmarkchanges = []
861 bmarkchanges = []
862 for oldnode, newnode in moves.items():
862 for oldnode, newnode in moves.items():
863 oldbmarks = repo.nodebookmarks(oldnode)
863 oldbmarks = repo.nodebookmarks(oldnode)
864 if not oldbmarks:
864 if not oldbmarks:
865 continue
865 continue
866 from . import bookmarks # avoid import cycle
866 from . import bookmarks # avoid import cycle
867 repo.ui.debug('moving bookmarks %r from %s to %s\n' %
867 repo.ui.debug('moving bookmarks %r from %s to %s\n' %
868 (pycompat.rapply(pycompat.maybebytestr, oldbmarks),
868 (pycompat.rapply(pycompat.maybebytestr, oldbmarks),
869 hex(oldnode), hex(newnode)))
869 hex(oldnode), hex(newnode)))
870 # Delete divergent bookmarks being parents of related newnodes
870 # Delete divergent bookmarks being parents of related newnodes
871 deleterevs = repo.revs('parents(roots(%ln & (::%n))) - parents(%n)',
871 deleterevs = repo.revs('parents(roots(%ln & (::%n))) - parents(%n)',
872 allnewnodes, newnode, oldnode)
872 allnewnodes, newnode, oldnode)
873 deletenodes = _containsnode(repo, deleterevs)
873 deletenodes = _containsnode(repo, deleterevs)
874 for name in oldbmarks:
874 for name in oldbmarks:
875 bmarkchanges.append((name, newnode))
875 bmarkchanges.append((name, newnode))
876 for b in bookmarks.divergent2delete(repo, deletenodes, name):
876 for b in bookmarks.divergent2delete(repo, deletenodes, name):
877 bmarkchanges.append((b, None))
877 bmarkchanges.append((b, None))
878
878
879 if bmarkchanges:
879 if bmarkchanges:
880 bmarks.applychanges(repo, tr, bmarkchanges)
880 bmarks.applychanges(repo, tr, bmarkchanges)
881
881
882 for phase, nodes in toretract.items():
882 for phase, nodes in toretract.items():
883 phases.retractboundary(repo, tr, phase, nodes)
883 phases.retractboundary(repo, tr, phase, nodes)
884 for phase, nodes in toadvance.items():
884 for phase, nodes in toadvance.items():
885 phases.advanceboundary(repo, tr, phase, nodes)
885 phases.advanceboundary(repo, tr, phase, nodes)
886
886
887 # Obsolete or strip nodes
887 # Obsolete or strip nodes
888 if obsolete.isenabled(repo, obsolete.createmarkersopt):
888 if obsolete.isenabled(repo, obsolete.createmarkersopt):
889 # If a node is already obsoleted, and we want to obsolete it
889 # If a node is already obsoleted, and we want to obsolete it
890 # without a successor, skip that obssolete request since it's
890 # without a successor, skip that obssolete request since it's
891 # unnecessary. That's the "if s or not isobs(n)" check below.
891 # unnecessary. That's the "if s or not isobs(n)" check below.
892 # Also sort the node in topology order, that might be useful for
892 # Also sort the node in topology order, that might be useful for
893 # some obsstore logic.
893 # some obsstore logic.
894 # NOTE: the filtering and sorting might belong to createmarkers.
894 # NOTE: the filtering and sorting might belong to createmarkers.
895 isobs = unfi.obsstore.successors.__contains__
895 isobs = unfi.obsstore.successors.__contains__
896 torev = unfi.changelog.rev
896 torev = unfi.changelog.rev
897 sortfunc = lambda ns: torev(ns[0])
897 sortfunc = lambda ns: torev(ns[0])
898 rels = [(unfi[n], tuple(unfi[m] for m in s))
898 rels = [(unfi[n], tuple(unfi[m] for m in s))
899 for n, s in sorted(replacements.items(), key=sortfunc)
899 for n, s in sorted(replacements.items(), key=sortfunc)
900 if s or not isobs(n)]
900 if s or not isobs(n)]
901 if rels:
901 if rels:
902 obsolete.createmarkers(repo, rels, operation=operation,
902 obsolete.createmarkers(repo, rels, operation=operation,
903 metadata=metadata)
903 metadata=metadata)
904 else:
904 else:
905 from . import repair # avoid import cycle
905 from . import repair # avoid import cycle
906 tostrip = list(replacements)
906 tostrip = list(replacements)
907 if tostrip:
907 if tostrip:
908 repair.delayedstrip(repo.ui, repo, tostrip, operation)
908 repair.delayedstrip(repo.ui, repo, tostrip, operation)
909
909
910 def addremove(repo, matcher, prefix, opts=None):
910 def addremove(repo, matcher, prefix, opts=None):
911 if opts is None:
911 if opts is None:
912 opts = {}
912 opts = {}
913 m = matcher
913 m = matcher
914 dry_run = opts.get('dry_run')
914 dry_run = opts.get('dry_run')
915 try:
915 try:
916 similarity = float(opts.get('similarity') or 0)
916 similarity = float(opts.get('similarity') or 0)
917 except ValueError:
917 except ValueError:
918 raise error.Abort(_('similarity must be a number'))
918 raise error.Abort(_('similarity must be a number'))
919 if similarity < 0 or similarity > 100:
919 if similarity < 0 or similarity > 100:
920 raise error.Abort(_('similarity must be between 0 and 100'))
920 raise error.Abort(_('similarity must be between 0 and 100'))
921 similarity /= 100.0
921 similarity /= 100.0
922
922
923 ret = 0
923 ret = 0
924 join = lambda f: os.path.join(prefix, f)
924 join = lambda f: os.path.join(prefix, f)
925
925
926 wctx = repo[None]
926 wctx = repo[None]
927 for subpath in sorted(wctx.substate):
927 for subpath in sorted(wctx.substate):
928 submatch = matchmod.subdirmatcher(subpath, m)
928 submatch = matchmod.subdirmatcher(subpath, m)
929 if opts.get('subrepos') or m.exact(subpath) or any(submatch.files()):
929 if opts.get('subrepos') or m.exact(subpath) or any(submatch.files()):
930 sub = wctx.sub(subpath)
930 sub = wctx.sub(subpath)
931 try:
931 try:
932 if sub.addremove(submatch, prefix, opts):
932 if sub.addremove(submatch, prefix, opts):
933 ret = 1
933 ret = 1
934 except error.LookupError:
934 except error.LookupError:
935 repo.ui.status(_("skipping missing subrepository: %s\n")
935 repo.ui.status(_("skipping missing subrepository: %s\n")
936 % join(subpath))
936 % join(subpath))
937
937
938 rejected = []
938 rejected = []
939 def badfn(f, msg):
939 def badfn(f, msg):
940 if f in m.files():
940 if f in m.files():
941 m.bad(f, msg)
941 m.bad(f, msg)
942 rejected.append(f)
942 rejected.append(f)
943
943
944 badmatch = matchmod.badmatch(m, badfn)
944 badmatch = matchmod.badmatch(m, badfn)
945 added, unknown, deleted, removed, forgotten = _interestingfiles(repo,
945 added, unknown, deleted, removed, forgotten = _interestingfiles(repo,
946 badmatch)
946 badmatch)
947
947
948 unknownset = set(unknown + forgotten)
948 unknownset = set(unknown + forgotten)
949 toprint = unknownset.copy()
949 toprint = unknownset.copy()
950 toprint.update(deleted)
950 toprint.update(deleted)
951 for abs in sorted(toprint):
951 for abs in sorted(toprint):
952 if repo.ui.verbose or not m.exact(abs):
952 if repo.ui.verbose or not m.exact(abs):
953 if abs in unknownset:
953 if abs in unknownset:
954 status = _('adding %s\n') % m.uipath(abs)
954 status = _('adding %s\n') % m.uipath(abs)
955 else:
955 else:
956 status = _('removing %s\n') % m.uipath(abs)
956 status = _('removing %s\n') % m.uipath(abs)
957 repo.ui.status(status)
957 repo.ui.status(status)
958
958
959 renames = _findrenames(repo, m, added + unknown, removed + deleted,
959 renames = _findrenames(repo, m, added + unknown, removed + deleted,
960 similarity)
960 similarity)
961
961
962 if not dry_run:
962 if not dry_run:
963 _markchanges(repo, unknown + forgotten, deleted, renames)
963 _markchanges(repo, unknown + forgotten, deleted, renames)
964
964
965 for f in rejected:
965 for f in rejected:
966 if f in m.files():
966 if f in m.files():
967 return 1
967 return 1
968 return ret
968 return ret
969
969
970 def marktouched(repo, files, similarity=0.0):
970 def marktouched(repo, files, similarity=0.0):
971 '''Assert that files have somehow been operated upon. files are relative to
971 '''Assert that files have somehow been operated upon. files are relative to
972 the repo root.'''
972 the repo root.'''
973 m = matchfiles(repo, files, badfn=lambda x, y: rejected.append(x))
973 m = matchfiles(repo, files, badfn=lambda x, y: rejected.append(x))
974 rejected = []
974 rejected = []
975
975
976 added, unknown, deleted, removed, forgotten = _interestingfiles(repo, m)
976 added, unknown, deleted, removed, forgotten = _interestingfiles(repo, m)
977
977
978 if repo.ui.verbose:
978 if repo.ui.verbose:
979 unknownset = set(unknown + forgotten)
979 unknownset = set(unknown + forgotten)
980 toprint = unknownset.copy()
980 toprint = unknownset.copy()
981 toprint.update(deleted)
981 toprint.update(deleted)
982 for abs in sorted(toprint):
982 for abs in sorted(toprint):
983 if abs in unknownset:
983 if abs in unknownset:
984 status = _('adding %s\n') % abs
984 status = _('adding %s\n') % abs
985 else:
985 else:
986 status = _('removing %s\n') % abs
986 status = _('removing %s\n') % abs
987 repo.ui.status(status)
987 repo.ui.status(status)
988
988
989 renames = _findrenames(repo, m, added + unknown, removed + deleted,
989 renames = _findrenames(repo, m, added + unknown, removed + deleted,
990 similarity)
990 similarity)
991
991
992 _markchanges(repo, unknown + forgotten, deleted, renames)
992 _markchanges(repo, unknown + forgotten, deleted, renames)
993
993
994 for f in rejected:
994 for f in rejected:
995 if f in m.files():
995 if f in m.files():
996 return 1
996 return 1
997 return 0
997 return 0
998
998
999 def _interestingfiles(repo, matcher):
999 def _interestingfiles(repo, matcher):
1000 '''Walk dirstate with matcher, looking for files that addremove would care
1000 '''Walk dirstate with matcher, looking for files that addremove would care
1001 about.
1001 about.
1002
1002
1003 This is different from dirstate.status because it doesn't care about
1003 This is different from dirstate.status because it doesn't care about
1004 whether files are modified or clean.'''
1004 whether files are modified or clean.'''
1005 added, unknown, deleted, removed, forgotten = [], [], [], [], []
1005 added, unknown, deleted, removed, forgotten = [], [], [], [], []
1006 audit_path = pathutil.pathauditor(repo.root, cached=True)
1006 audit_path = pathutil.pathauditor(repo.root, cached=True)
1007
1007
1008 ctx = repo[None]
1008 ctx = repo[None]
1009 dirstate = repo.dirstate
1009 dirstate = repo.dirstate
1010 walkresults = dirstate.walk(matcher, subrepos=sorted(ctx.substate),
1010 walkresults = dirstate.walk(matcher, subrepos=sorted(ctx.substate),
1011 unknown=True, ignored=False, full=False)
1011 unknown=True, ignored=False, full=False)
1012 for abs, st in walkresults.iteritems():
1012 for abs, st in walkresults.iteritems():
1013 dstate = dirstate[abs]
1013 dstate = dirstate[abs]
1014 if dstate == '?' and audit_path.check(abs):
1014 if dstate == '?' and audit_path.check(abs):
1015 unknown.append(abs)
1015 unknown.append(abs)
1016 elif dstate != 'r' and not st:
1016 elif dstate != 'r' and not st:
1017 deleted.append(abs)
1017 deleted.append(abs)
1018 elif dstate == 'r' and st:
1018 elif dstate == 'r' and st:
1019 forgotten.append(abs)
1019 forgotten.append(abs)
1020 # for finding renames
1020 # for finding renames
1021 elif dstate == 'r' and not st:
1021 elif dstate == 'r' and not st:
1022 removed.append(abs)
1022 removed.append(abs)
1023 elif dstate == 'a':
1023 elif dstate == 'a':
1024 added.append(abs)
1024 added.append(abs)
1025
1025
1026 return added, unknown, deleted, removed, forgotten
1026 return added, unknown, deleted, removed, forgotten
1027
1027
1028 def _findrenames(repo, matcher, added, removed, similarity):
1028 def _findrenames(repo, matcher, added, removed, similarity):
1029 '''Find renames from removed files to added ones.'''
1029 '''Find renames from removed files to added ones.'''
1030 renames = {}
1030 renames = {}
1031 if similarity > 0:
1031 if similarity > 0:
1032 for old, new, score in similar.findrenames(repo, added, removed,
1032 for old, new, score in similar.findrenames(repo, added, removed,
1033 similarity):
1033 similarity):
1034 if (repo.ui.verbose or not matcher.exact(old)
1034 if (repo.ui.verbose or not matcher.exact(old)
1035 or not matcher.exact(new)):
1035 or not matcher.exact(new)):
1036 repo.ui.status(_('recording removal of %s as rename to %s '
1036 repo.ui.status(_('recording removal of %s as rename to %s '
1037 '(%d%% similar)\n') %
1037 '(%d%% similar)\n') %
1038 (matcher.rel(old), matcher.rel(new),
1038 (matcher.rel(old), matcher.rel(new),
1039 score * 100))
1039 score * 100))
1040 renames[new] = old
1040 renames[new] = old
1041 return renames
1041 return renames
1042
1042
1043 def _markchanges(repo, unknown, deleted, renames):
1043 def _markchanges(repo, unknown, deleted, renames):
1044 '''Marks the files in unknown as added, the files in deleted as removed,
1044 '''Marks the files in unknown as added, the files in deleted as removed,
1045 and the files in renames as copied.'''
1045 and the files in renames as copied.'''
1046 wctx = repo[None]
1046 wctx = repo[None]
1047 with repo.wlock():
1047 with repo.wlock():
1048 wctx.forget(deleted)
1048 wctx.forget(deleted)
1049 wctx.add(unknown)
1049 wctx.add(unknown)
1050 for new, old in renames.iteritems():
1050 for new, old in renames.iteritems():
1051 wctx.copy(old, new)
1051 wctx.copy(old, new)
1052
1052
1053 def dirstatecopy(ui, repo, wctx, src, dst, dryrun=False, cwd=None):
1053 def dirstatecopy(ui, repo, wctx, src, dst, dryrun=False, cwd=None):
1054 """Update the dirstate to reflect the intent of copying src to dst. For
1054 """Update the dirstate to reflect the intent of copying src to dst. For
1055 different reasons it might not end with dst being marked as copied from src.
1055 different reasons it might not end with dst being marked as copied from src.
1056 """
1056 """
1057 origsrc = repo.dirstate.copied(src) or src
1057 origsrc = repo.dirstate.copied(src) or src
1058 if dst == origsrc: # copying back a copy?
1058 if dst == origsrc: # copying back a copy?
1059 if repo.dirstate[dst] not in 'mn' and not dryrun:
1059 if repo.dirstate[dst] not in 'mn' and not dryrun:
1060 repo.dirstate.normallookup(dst)
1060 repo.dirstate.normallookup(dst)
1061 else:
1061 else:
1062 if repo.dirstate[origsrc] == 'a' and origsrc == src:
1062 if repo.dirstate[origsrc] == 'a' and origsrc == src:
1063 if not ui.quiet:
1063 if not ui.quiet:
1064 ui.warn(_("%s has not been committed yet, so no copy "
1064 ui.warn(_("%s has not been committed yet, so no copy "
1065 "data will be stored for %s.\n")
1065 "data will be stored for %s.\n")
1066 % (repo.pathto(origsrc, cwd), repo.pathto(dst, cwd)))
1066 % (repo.pathto(origsrc, cwd), repo.pathto(dst, cwd)))
1067 if repo.dirstate[dst] in '?r' and not dryrun:
1067 if repo.dirstate[dst] in '?r' and not dryrun:
1068 wctx.add([dst])
1068 wctx.add([dst])
1069 elif not dryrun:
1069 elif not dryrun:
1070 wctx.copy(origsrc, dst)
1070 wctx.copy(origsrc, dst)
1071
1071
1072 def readrequires(opener, supported):
1072 def readrequires(opener, supported):
1073 '''Reads and parses .hg/requires and checks if all entries found
1073 '''Reads and parses .hg/requires and checks if all entries found
1074 are in the list of supported features.'''
1074 are in the list of supported features.'''
1075 requirements = set(opener.read("requires").splitlines())
1075 requirements = set(opener.read("requires").splitlines())
1076 missings = []
1076 missings = []
1077 for r in requirements:
1077 for r in requirements:
1078 if r not in supported:
1078 if r not in supported:
1079 if not r or not r[0:1].isalnum():
1079 if not r or not r[0:1].isalnum():
1080 raise error.RequirementError(_(".hg/requires file is corrupt"))
1080 raise error.RequirementError(_(".hg/requires file is corrupt"))
1081 missings.append(r)
1081 missings.append(r)
1082 missings.sort()
1082 missings.sort()
1083 if missings:
1083 if missings:
1084 raise error.RequirementError(
1084 raise error.RequirementError(
1085 _("repository requires features unknown to this Mercurial: %s")
1085 _("repository requires features unknown to this Mercurial: %s")
1086 % " ".join(missings),
1086 % " ".join(missings),
1087 hint=_("see https://mercurial-scm.org/wiki/MissingRequirement"
1087 hint=_("see https://mercurial-scm.org/wiki/MissingRequirement"
1088 " for more information"))
1088 " for more information"))
1089 return requirements
1089 return requirements
1090
1090
1091 def writerequires(opener, requirements):
1091 def writerequires(opener, requirements):
1092 with opener('requires', 'w') as fp:
1092 with opener('requires', 'w') as fp:
1093 for r in sorted(requirements):
1093 for r in sorted(requirements):
1094 fp.write("%s\n" % r)
1094 fp.write("%s\n" % r)
1095
1095
1096 class filecachesubentry(object):
1096 class filecachesubentry(object):
1097 def __init__(self, path, stat):
1097 def __init__(self, path, stat):
1098 self.path = path
1098 self.path = path
1099 self.cachestat = None
1099 self.cachestat = None
1100 self._cacheable = None
1100 self._cacheable = None
1101
1101
1102 if stat:
1102 if stat:
1103 self.cachestat = filecachesubentry.stat(self.path)
1103 self.cachestat = filecachesubentry.stat(self.path)
1104
1104
1105 if self.cachestat:
1105 if self.cachestat:
1106 self._cacheable = self.cachestat.cacheable()
1106 self._cacheable = self.cachestat.cacheable()
1107 else:
1107 else:
1108 # None means we don't know yet
1108 # None means we don't know yet
1109 self._cacheable = None
1109 self._cacheable = None
1110
1110
1111 def refresh(self):
1111 def refresh(self):
1112 if self.cacheable():
1112 if self.cacheable():
1113 self.cachestat = filecachesubentry.stat(self.path)
1113 self.cachestat = filecachesubentry.stat(self.path)
1114
1114
1115 def cacheable(self):
1115 def cacheable(self):
1116 if self._cacheable is not None:
1116 if self._cacheable is not None:
1117 return self._cacheable
1117 return self._cacheable
1118
1118
1119 # we don't know yet, assume it is for now
1119 # we don't know yet, assume it is for now
1120 return True
1120 return True
1121
1121
1122 def changed(self):
1122 def changed(self):
1123 # no point in going further if we can't cache it
1123 # no point in going further if we can't cache it
1124 if not self.cacheable():
1124 if not self.cacheable():
1125 return True
1125 return True
1126
1126
1127 newstat = filecachesubentry.stat(self.path)
1127 newstat = filecachesubentry.stat(self.path)
1128
1128
1129 # we may not know if it's cacheable yet, check again now
1129 # we may not know if it's cacheable yet, check again now
1130 if newstat and self._cacheable is None:
1130 if newstat and self._cacheable is None:
1131 self._cacheable = newstat.cacheable()
1131 self._cacheable = newstat.cacheable()
1132
1132
1133 # check again
1133 # check again
1134 if not self._cacheable:
1134 if not self._cacheable:
1135 return True
1135 return True
1136
1136
1137 if self.cachestat != newstat:
1137 if self.cachestat != newstat:
1138 self.cachestat = newstat
1138 self.cachestat = newstat
1139 return True
1139 return True
1140 else:
1140 else:
1141 return False
1141 return False
1142
1142
1143 @staticmethod
1143 @staticmethod
1144 def stat(path):
1144 def stat(path):
1145 try:
1145 try:
1146 return util.cachestat(path)
1146 return util.cachestat(path)
1147 except OSError as e:
1147 except OSError as e:
1148 if e.errno != errno.ENOENT:
1148 if e.errno != errno.ENOENT:
1149 raise
1149 raise
1150
1150
1151 class filecacheentry(object):
1151 class filecacheentry(object):
1152 def __init__(self, paths, stat=True):
1152 def __init__(self, paths, stat=True):
1153 self._entries = []
1153 self._entries = []
1154 for path in paths:
1154 for path in paths:
1155 self._entries.append(filecachesubentry(path, stat))
1155 self._entries.append(filecachesubentry(path, stat))
1156
1156
1157 def changed(self):
1157 def changed(self):
1158 '''true if any entry has changed'''
1158 '''true if any entry has changed'''
1159 for entry in self._entries:
1159 for entry in self._entries:
1160 if entry.changed():
1160 if entry.changed():
1161 return True
1161 return True
1162 return False
1162 return False
1163
1163
1164 def refresh(self):
1164 def refresh(self):
1165 for entry in self._entries:
1165 for entry in self._entries:
1166 entry.refresh()
1166 entry.refresh()
1167
1167
1168 class filecache(object):
1168 class filecache(object):
1169 """A property like decorator that tracks files under .hg/ for updates.
1169 """A property like decorator that tracks files under .hg/ for updates.
1170
1170
1171 On first access, the files defined as arguments are stat()ed and the
1171 On first access, the files defined as arguments are stat()ed and the
1172 results cached. The decorated function is called. The results are stashed
1172 results cached. The decorated function is called. The results are stashed
1173 away in a ``_filecache`` dict on the object whose method is decorated.
1173 away in a ``_filecache`` dict on the object whose method is decorated.
1174
1174
1175 On subsequent access, the cached result is returned.
1175 On subsequent access, the cached result is returned.
1176
1176
1177 On external property set operations, stat() calls are performed and the new
1177 On external property set operations, stat() calls are performed and the new
1178 value is cached.
1178 value is cached.
1179
1179
1180 On property delete operations, cached data is removed.
1180 On property delete operations, cached data is removed.
1181
1181
1182 When using the property API, cached data is always returned, if available:
1182 When using the property API, cached data is always returned, if available:
1183 no stat() is performed to check if the file has changed and if the function
1183 no stat() is performed to check if the file has changed and if the function
1184 needs to be called to reflect file changes.
1184 needs to be called to reflect file changes.
1185
1185
1186 Others can muck about with the state of the ``_filecache`` dict. e.g. they
1186 Others can muck about with the state of the ``_filecache`` dict. e.g. they
1187 can populate an entry before the property's getter is called. In this case,
1187 can populate an entry before the property's getter is called. In this case,
1188 entries in ``_filecache`` will be used during property operations,
1188 entries in ``_filecache`` will be used during property operations,
1189 if available. If the underlying file changes, it is up to external callers
1189 if available. If the underlying file changes, it is up to external callers
1190 to reflect this by e.g. calling ``delattr(obj, attr)`` to remove the cached
1190 to reflect this by e.g. calling ``delattr(obj, attr)`` to remove the cached
1191 method result as well as possibly calling ``del obj._filecache[attr]`` to
1191 method result as well as possibly calling ``del obj._filecache[attr]`` to
1192 remove the ``filecacheentry``.
1192 remove the ``filecacheentry``.
1193 """
1193 """
1194
1194
1195 def __init__(self, *paths):
1195 def __init__(self, *paths):
1196 self.paths = paths
1196 self.paths = paths
1197
1197
1198 def join(self, obj, fname):
1198 def join(self, obj, fname):
1199 """Used to compute the runtime path of a cached file.
1199 """Used to compute the runtime path of a cached file.
1200
1200
1201 Users should subclass filecache and provide their own version of this
1201 Users should subclass filecache and provide their own version of this
1202 function to call the appropriate join function on 'obj' (an instance
1202 function to call the appropriate join function on 'obj' (an instance
1203 of the class that its member function was decorated).
1203 of the class that its member function was decorated).
1204 """
1204 """
1205 raise NotImplementedError
1205 raise NotImplementedError
1206
1206
1207 def __call__(self, func):
1207 def __call__(self, func):
1208 self.func = func
1208 self.func = func
1209 self.sname = func.__name__
1209 self.sname = func.__name__
1210 self.name = pycompat.sysbytes(self.sname)
1210 self.name = pycompat.sysbytes(self.sname)
1211 return self
1211 return self
1212
1212
1213 def __get__(self, obj, type=None):
1213 def __get__(self, obj, type=None):
1214 # if accessed on the class, return the descriptor itself.
1214 # if accessed on the class, return the descriptor itself.
1215 if obj is None:
1215 if obj is None:
1216 return self
1216 return self
1217 # do we need to check if the file changed?
1217 # do we need to check if the file changed?
1218 if self.sname in obj.__dict__:
1218 if self.sname in obj.__dict__:
1219 assert self.name in obj._filecache, self.name
1219 assert self.name in obj._filecache, self.name
1220 return obj.__dict__[self.sname]
1220 return obj.__dict__[self.sname]
1221
1221
1222 entry = obj._filecache.get(self.name)
1222 entry = obj._filecache.get(self.name)
1223
1223
1224 if entry:
1224 if entry:
1225 if entry.changed():
1225 if entry.changed():
1226 entry.obj = self.func(obj)
1226 entry.obj = self.func(obj)
1227 else:
1227 else:
1228 paths = [self.join(obj, path) for path in self.paths]
1228 paths = [self.join(obj, path) for path in self.paths]
1229
1229
1230 # We stat -before- creating the object so our cache doesn't lie if
1230 # We stat -before- creating the object so our cache doesn't lie if
1231 # a writer modified between the time we read and stat
1231 # a writer modified between the time we read and stat
1232 entry = filecacheentry(paths, True)
1232 entry = filecacheentry(paths, True)
1233 entry.obj = self.func(obj)
1233 entry.obj = self.func(obj)
1234
1234
1235 obj._filecache[self.name] = entry
1235 obj._filecache[self.name] = entry
1236
1236
1237 obj.__dict__[self.sname] = entry.obj
1237 obj.__dict__[self.sname] = entry.obj
1238 return entry.obj
1238 return entry.obj
1239
1239
1240 def __set__(self, obj, value):
1240 def __set__(self, obj, value):
1241 if self.name not in obj._filecache:
1241 if self.name not in obj._filecache:
1242 # we add an entry for the missing value because X in __dict__
1242 # we add an entry for the missing value because X in __dict__
1243 # implies X in _filecache
1243 # implies X in _filecache
1244 paths = [self.join(obj, path) for path in self.paths]
1244 paths = [self.join(obj, path) for path in self.paths]
1245 ce = filecacheentry(paths, False)
1245 ce = filecacheentry(paths, False)
1246 obj._filecache[self.name] = ce
1246 obj._filecache[self.name] = ce
1247 else:
1247 else:
1248 ce = obj._filecache[self.name]
1248 ce = obj._filecache[self.name]
1249
1249
1250 ce.obj = value # update cached copy
1250 ce.obj = value # update cached copy
1251 obj.__dict__[self.sname] = value # update copy returned by obj.x
1251 obj.__dict__[self.sname] = value # update copy returned by obj.x
1252
1252
1253 def __delete__(self, obj):
1253 def __delete__(self, obj):
1254 try:
1254 try:
1255 del obj.__dict__[self.sname]
1255 del obj.__dict__[self.sname]
1256 except KeyError:
1256 except KeyError:
1257 raise AttributeError(self.sname)
1257 raise AttributeError(self.sname)
1258
1258
1259 def extdatasource(repo, source):
1259 def extdatasource(repo, source):
1260 """Gather a map of rev -> value dict from the specified source
1260 """Gather a map of rev -> value dict from the specified source
1261
1261
1262 A source spec is treated as a URL, with a special case shell: type
1262 A source spec is treated as a URL, with a special case shell: type
1263 for parsing the output from a shell command.
1263 for parsing the output from a shell command.
1264
1264
1265 The data is parsed as a series of newline-separated records where
1265 The data is parsed as a series of newline-separated records where
1266 each record is a revision specifier optionally followed by a space
1266 each record is a revision specifier optionally followed by a space
1267 and a freeform string value. If the revision is known locally, it
1267 and a freeform string value. If the revision is known locally, it
1268 is converted to a rev, otherwise the record is skipped.
1268 is converted to a rev, otherwise the record is skipped.
1269
1269
1270 Note that both key and value are treated as UTF-8 and converted to
1270 Note that both key and value are treated as UTF-8 and converted to
1271 the local encoding. This allows uniformity between local and
1271 the local encoding. This allows uniformity between local and
1272 remote data sources.
1272 remote data sources.
1273 """
1273 """
1274
1274
1275 spec = repo.ui.config("extdata", source)
1275 spec = repo.ui.config("extdata", source)
1276 if not spec:
1276 if not spec:
1277 raise error.Abort(_("unknown extdata source '%s'") % source)
1277 raise error.Abort(_("unknown extdata source '%s'") % source)
1278
1278
1279 data = {}
1279 data = {}
1280 src = proc = None
1280 src = proc = None
1281 try:
1281 try:
1282 if spec.startswith("shell:"):
1282 if spec.startswith("shell:"):
1283 # external commands should be run relative to the repo root
1283 # external commands should be run relative to the repo root
1284 cmd = spec[6:]
1284 cmd = spec[6:]
1285 proc = subprocess.Popen(cmd, shell=True, bufsize=-1,
1285 proc = subprocess.Popen(cmd, shell=True, bufsize=-1,
1286 close_fds=procutil.closefds,
1286 close_fds=procutil.closefds,
1287 stdout=subprocess.PIPE, cwd=repo.root)
1287 stdout=subprocess.PIPE, cwd=repo.root)
1288 src = proc.stdout
1288 src = proc.stdout
1289 else:
1289 else:
1290 # treat as a URL or file
1290 # treat as a URL or file
1291 src = url.open(repo.ui, spec)
1291 src = url.open(repo.ui, spec)
1292 for l in src:
1292 for l in src:
1293 if " " in l:
1293 if " " in l:
1294 k, v = l.strip().split(" ", 1)
1294 k, v = l.strip().split(" ", 1)
1295 else:
1295 else:
1296 k, v = l.strip(), ""
1296 k, v = l.strip(), ""
1297
1297
1298 k = encoding.tolocal(k)
1298 k = encoding.tolocal(k)
1299 try:
1299 try:
1300 data[revsingle(repo, k).rev()] = encoding.tolocal(v)
1300 data[revsingle(repo, k).rev()] = encoding.tolocal(v)
1301 except (error.LookupError, error.RepoLookupError):
1301 except (error.LookupError, error.RepoLookupError):
1302 pass # we ignore data for nodes that don't exist locally
1302 pass # we ignore data for nodes that don't exist locally
1303 finally:
1303 finally:
1304 if proc:
1304 if proc:
1305 proc.communicate()
1305 proc.communicate()
1306 if src:
1306 if src:
1307 src.close()
1307 src.close()
1308 if proc and proc.returncode != 0:
1308 if proc and proc.returncode != 0:
1309 raise error.Abort(_("extdata command '%s' failed: %s")
1309 raise error.Abort(_("extdata command '%s' failed: %s")
1310 % (cmd, procutil.explainexit(proc.returncode)))
1310 % (cmd, procutil.explainexit(proc.returncode)))
1311
1311
1312 return data
1312 return data
1313
1313
1314 def _locksub(repo, lock, envvar, cmd, environ=None, *args, **kwargs):
1314 def _locksub(repo, lock, envvar, cmd, environ=None, *args, **kwargs):
1315 if lock is None:
1315 if lock is None:
1316 raise error.LockInheritanceContractViolation(
1316 raise error.LockInheritanceContractViolation(
1317 'lock can only be inherited while held')
1317 'lock can only be inherited while held')
1318 if environ is None:
1318 if environ is None:
1319 environ = {}
1319 environ = {}
1320 with lock.inherit() as locker:
1320 with lock.inherit() as locker:
1321 environ[envvar] = locker
1321 environ[envvar] = locker
1322 return repo.ui.system(cmd, environ=environ, *args, **kwargs)
1322 return repo.ui.system(cmd, environ=environ, *args, **kwargs)
1323
1323
1324 def wlocksub(repo, cmd, *args, **kwargs):
1324 def wlocksub(repo, cmd, *args, **kwargs):
1325 """run cmd as a subprocess that allows inheriting repo's wlock
1325 """run cmd as a subprocess that allows inheriting repo's wlock
1326
1326
1327 This can only be called while the wlock is held. This takes all the
1327 This can only be called while the wlock is held. This takes all the
1328 arguments that ui.system does, and returns the exit code of the
1328 arguments that ui.system does, and returns the exit code of the
1329 subprocess."""
1329 subprocess."""
1330 return _locksub(repo, repo.currentwlock(), 'HG_WLOCK_LOCKER', cmd, *args,
1330 return _locksub(repo, repo.currentwlock(), 'HG_WLOCK_LOCKER', cmd, *args,
1331 **kwargs)
1331 **kwargs)
1332
1332
1333 class progress(object):
1333 class progress(object):
1334 def __init__(self, ui, topic, unit="", total=None):
1334 def __init__(self, ui, topic, unit="", total=None):
1335 self.ui = ui
1335 self.ui = ui
1336 self.pos = 0
1336 self.pos = 0
1337 self.topic = topic
1337 self.topic = topic
1338 self.unit = unit
1338 self.unit = unit
1339 self.total = total
1339 self.total = total
1340
1340
1341 def __enter__(self):
1341 def __enter__(self):
1342 return self
1342 return self
1343
1343
1344 def __exit__(self, exc_type, exc_value, exc_tb):
1344 def __exit__(self, exc_type, exc_value, exc_tb):
1345 self.complete()
1345 self.complete()
1346
1346
1347 def update(self, pos, item="", total=None):
1347 def update(self, pos, item="", total=None):
1348 assert pos is not None
1348 assert pos is not None
1349 if total:
1349 if total:
1350 self.total = total
1350 self.total = total
1351 self.pos = pos
1351 self.pos = pos
1352 self._print(item)
1352 self._print(item)
1353
1353
1354 def increment(self, step=1, item="", total=None):
1354 def increment(self, step=1, item="", total=None):
1355 self.update(self.pos + step, item, total)
1355 self.update(self.pos + step, item, total)
1356
1356
1357 def complete(self):
1357 def complete(self):
1358 self.ui.progress(self.topic, None)
1358 self.ui.progress(self.topic, None)
1359
1359
1360 def _print(self, item):
1360 def _print(self, item):
1361 self.ui.progress(self.topic, self.pos, item, self.unit,
1361 self.ui.progress(self.topic, self.pos, item, self.unit,
1362 self.total)
1362 self.total)
1363
1363
1364 def gdinitconfig(ui):
1364 def gdinitconfig(ui):
1365 """helper function to know if a repo should be created as general delta
1365 """helper function to know if a repo should be created as general delta
1366 """
1366 """
1367 # experimental config: format.generaldelta
1367 # experimental config: format.generaldelta
1368 return (ui.configbool('format', 'generaldelta')
1368 return (ui.configbool('format', 'generaldelta')
1369 or ui.configbool('format', 'usegeneraldelta')
1369 or ui.configbool('format', 'usegeneraldelta')
1370 or ui.configbool('format', 'sparse-revlog'))
1370 or ui.configbool('format', 'sparse-revlog'))
1371
1371
1372 def gddeltaconfig(ui):
1372 def gddeltaconfig(ui):
1373 """helper function to know if incoming delta should be optimised
1373 """helper function to know if incoming delta should be optimised
1374 """
1374 """
1375 # experimental config: format.generaldelta
1375 # experimental config: format.generaldelta
1376 return ui.configbool('format', 'generaldelta')
1376 return ui.configbool('format', 'generaldelta')
1377
1377
1378 class simplekeyvaluefile(object):
1378 class simplekeyvaluefile(object):
1379 """A simple file with key=value lines
1379 """A simple file with key=value lines
1380
1380
1381 Keys must be alphanumerics and start with a letter, values must not
1381 Keys must be alphanumerics and start with a letter, values must not
1382 contain '\n' characters"""
1382 contain '\n' characters"""
1383 firstlinekey = '__firstline'
1383 firstlinekey = '__firstline'
1384
1384
1385 def __init__(self, vfs, path, keys=None):
1385 def __init__(self, vfs, path, keys=None):
1386 self.vfs = vfs
1386 self.vfs = vfs
1387 self.path = path
1387 self.path = path
1388
1388
1389 def read(self, firstlinenonkeyval=False):
1389 def read(self, firstlinenonkeyval=False):
1390 """Read the contents of a simple key-value file
1390 """Read the contents of a simple key-value file
1391
1391
1392 'firstlinenonkeyval' indicates whether the first line of file should
1392 'firstlinenonkeyval' indicates whether the first line of file should
1393 be treated as a key-value pair or reuturned fully under the
1393 be treated as a key-value pair or reuturned fully under the
1394 __firstline key."""
1394 __firstline key."""
1395 lines = self.vfs.readlines(self.path)
1395 lines = self.vfs.readlines(self.path)
1396 d = {}
1396 d = {}
1397 if firstlinenonkeyval:
1397 if firstlinenonkeyval:
1398 if not lines:
1398 if not lines:
1399 e = _("empty simplekeyvalue file")
1399 e = _("empty simplekeyvalue file")
1400 raise error.CorruptedState(e)
1400 raise error.CorruptedState(e)
1401 # we don't want to include '\n' in the __firstline
1401 # we don't want to include '\n' in the __firstline
1402 d[self.firstlinekey] = lines[0][:-1]
1402 d[self.firstlinekey] = lines[0][:-1]
1403 del lines[0]
1403 del lines[0]
1404
1404
1405 try:
1405 try:
1406 # the 'if line.strip()' part prevents us from failing on empty
1406 # the 'if line.strip()' part prevents us from failing on empty
1407 # lines which only contain '\n' therefore are not skipped
1407 # lines which only contain '\n' therefore are not skipped
1408 # by 'if line'
1408 # by 'if line'
1409 updatedict = dict(line[:-1].split('=', 1) for line in lines
1409 updatedict = dict(line[:-1].split('=', 1) for line in lines
1410 if line.strip())
1410 if line.strip())
1411 if self.firstlinekey in updatedict:
1411 if self.firstlinekey in updatedict:
1412 e = _("%r can't be used as a key")
1412 e = _("%r can't be used as a key")
1413 raise error.CorruptedState(e % self.firstlinekey)
1413 raise error.CorruptedState(e % self.firstlinekey)
1414 d.update(updatedict)
1414 d.update(updatedict)
1415 except ValueError as e:
1415 except ValueError as e:
1416 raise error.CorruptedState(str(e))
1416 raise error.CorruptedState(str(e))
1417 return d
1417 return d
1418
1418
1419 def write(self, data, firstline=None):
1419 def write(self, data, firstline=None):
1420 """Write key=>value mapping to a file
1420 """Write key=>value mapping to a file
1421 data is a dict. Keys must be alphanumerical and start with a letter.
1421 data is a dict. Keys must be alphanumerical and start with a letter.
1422 Values must not contain newline characters.
1422 Values must not contain newline characters.
1423
1423
1424 If 'firstline' is not None, it is written to file before
1424 If 'firstline' is not None, it is written to file before
1425 everything else, as it is, not in a key=value form"""
1425 everything else, as it is, not in a key=value form"""
1426 lines = []
1426 lines = []
1427 if firstline is not None:
1427 if firstline is not None:
1428 lines.append('%s\n' % firstline)
1428 lines.append('%s\n' % firstline)
1429
1429
1430 for k, v in data.items():
1430 for k, v in data.items():
1431 if k == self.firstlinekey:
1431 if k == self.firstlinekey:
1432 e = "key name '%s' is reserved" % self.firstlinekey
1432 e = "key name '%s' is reserved" % self.firstlinekey
1433 raise error.ProgrammingError(e)
1433 raise error.ProgrammingError(e)
1434 if not k[0:1].isalpha():
1434 if not k[0:1].isalpha():
1435 e = "keys must start with a letter in a key-value file"
1435 e = "keys must start with a letter in a key-value file"
1436 raise error.ProgrammingError(e)
1436 raise error.ProgrammingError(e)
1437 if not k.isalnum():
1437 if not k.isalnum():
1438 e = "invalid key name in a simple key-value file"
1438 e = "invalid key name in a simple key-value file"
1439 raise error.ProgrammingError(e)
1439 raise error.ProgrammingError(e)
1440 if '\n' in v:
1440 if '\n' in v:
1441 e = "invalid value in a simple key-value file"
1441 e = "invalid value in a simple key-value file"
1442 raise error.ProgrammingError(e)
1442 raise error.ProgrammingError(e)
1443 lines.append("%s=%s\n" % (k, v))
1443 lines.append("%s=%s\n" % (k, v))
1444 with self.vfs(self.path, mode='wb', atomictemp=True) as fp:
1444 with self.vfs(self.path, mode='wb', atomictemp=True) as fp:
1445 fp.write(''.join(lines))
1445 fp.write(''.join(lines))
1446
1446
1447 _reportobsoletedsource = [
1447 _reportobsoletedsource = [
1448 'debugobsolete',
1448 'debugobsolete',
1449 'pull',
1449 'pull',
1450 'push',
1450 'push',
1451 'serve',
1451 'serve',
1452 'unbundle',
1452 'unbundle',
1453 ]
1453 ]
1454
1454
1455 _reportnewcssource = [
1455 _reportnewcssource = [
1456 'pull',
1456 'pull',
1457 'unbundle',
1457 'unbundle',
1458 ]
1458 ]
1459
1459
1460 def prefetchfiles(repo, revs, match):
1460 def prefetchfiles(repo, revs, match):
1461 """Invokes the registered file prefetch functions, allowing extensions to
1461 """Invokes the registered file prefetch functions, allowing extensions to
1462 ensure the corresponding files are available locally, before the command
1462 ensure the corresponding files are available locally, before the command
1463 uses them."""
1463 uses them."""
1464 if match:
1464 if match:
1465 # The command itself will complain about files that don't exist, so
1465 # The command itself will complain about files that don't exist, so
1466 # don't duplicate the message.
1466 # don't duplicate the message.
1467 match = matchmod.badmatch(match, lambda fn, msg: None)
1467 match = matchmod.badmatch(match, lambda fn, msg: None)
1468 else:
1468 else:
1469 match = matchall(repo)
1469 match = matchall(repo)
1470
1470
1471 fileprefetchhooks(repo, revs, match)
1471 fileprefetchhooks(repo, revs, match)
1472
1472
1473 # a list of (repo, revs, match) prefetch functions
1473 # a list of (repo, revs, match) prefetch functions
1474 fileprefetchhooks = util.hooks()
1474 fileprefetchhooks = util.hooks()
1475
1475
1476 # A marker that tells the evolve extension to suppress its own reporting
1476 # A marker that tells the evolve extension to suppress its own reporting
1477 _reportstroubledchangesets = True
1477 _reportstroubledchangesets = True
1478
1478
1479 def registersummarycallback(repo, otr, txnname=''):
1479 def registersummarycallback(repo, otr, txnname=''):
1480 """register a callback to issue a summary after the transaction is closed
1480 """register a callback to issue a summary after the transaction is closed
1481 """
1481 """
1482 def txmatch(sources):
1482 def txmatch(sources):
1483 return any(txnname.startswith(source) for source in sources)
1483 return any(txnname.startswith(source) for source in sources)
1484
1484
1485 categories = []
1485 categories = []
1486
1486
1487 def reportsummary(func):
1487 def reportsummary(func):
1488 """decorator for report callbacks."""
1488 """decorator for report callbacks."""
1489 # The repoview life cycle is shorter than the one of the actual
1489 # The repoview life cycle is shorter than the one of the actual
1490 # underlying repository. So the filtered object can die before the
1490 # underlying repository. So the filtered object can die before the
1491 # weakref is used leading to troubles. We keep a reference to the
1491 # weakref is used leading to troubles. We keep a reference to the
1492 # unfiltered object and restore the filtering when retrieving the
1492 # unfiltered object and restore the filtering when retrieving the
1493 # repository through the weakref.
1493 # repository through the weakref.
1494 filtername = repo.filtername
1494 filtername = repo.filtername
1495 reporef = weakref.ref(repo.unfiltered())
1495 reporef = weakref.ref(repo.unfiltered())
1496 def wrapped(tr):
1496 def wrapped(tr):
1497 repo = reporef()
1497 repo = reporef()
1498 if filtername:
1498 if filtername:
1499 repo = repo.filtered(filtername)
1499 repo = repo.filtered(filtername)
1500 func(repo, tr)
1500 func(repo, tr)
1501 newcat = '%02i-txnreport' % len(categories)
1501 newcat = '%02i-txnreport' % len(categories)
1502 otr.addpostclose(newcat, wrapped)
1502 otr.addpostclose(newcat, wrapped)
1503 categories.append(newcat)
1503 categories.append(newcat)
1504 return wrapped
1504 return wrapped
1505
1505
1506 if txmatch(_reportobsoletedsource):
1506 if txmatch(_reportobsoletedsource):
1507 @reportsummary
1507 @reportsummary
1508 def reportobsoleted(repo, tr):
1508 def reportobsoleted(repo, tr):
1509 obsoleted = obsutil.getobsoleted(repo, tr)
1509 obsoleted = obsutil.getobsoleted(repo, tr)
1510 if obsoleted:
1510 if obsoleted:
1511 repo.ui.status(_('obsoleted %i changesets\n')
1511 repo.ui.status(_('obsoleted %i changesets\n')
1512 % len(obsoleted))
1512 % len(obsoleted))
1513
1513
1514 if (obsolete.isenabled(repo, obsolete.createmarkersopt) and
1514 if (obsolete.isenabled(repo, obsolete.createmarkersopt) and
1515 repo.ui.configbool('experimental', 'evolution.report-instabilities')):
1515 repo.ui.configbool('experimental', 'evolution.report-instabilities')):
1516 instabilitytypes = [
1516 instabilitytypes = [
1517 ('orphan', 'orphan'),
1517 ('orphan', 'orphan'),
1518 ('phase-divergent', 'phasedivergent'),
1518 ('phase-divergent', 'phasedivergent'),
1519 ('content-divergent', 'contentdivergent'),
1519 ('content-divergent', 'contentdivergent'),
1520 ]
1520 ]
1521
1521
1522 def getinstabilitycounts(repo):
1522 def getinstabilitycounts(repo):
1523 filtered = repo.changelog.filteredrevs
1523 filtered = repo.changelog.filteredrevs
1524 counts = {}
1524 counts = {}
1525 for instability, revset in instabilitytypes:
1525 for instability, revset in instabilitytypes:
1526 counts[instability] = len(set(obsolete.getrevs(repo, revset)) -
1526 counts[instability] = len(set(obsolete.getrevs(repo, revset)) -
1527 filtered)
1527 filtered)
1528 return counts
1528 return counts
1529
1529
1530 oldinstabilitycounts = getinstabilitycounts(repo)
1530 oldinstabilitycounts = getinstabilitycounts(repo)
1531 @reportsummary
1531 @reportsummary
1532 def reportnewinstabilities(repo, tr):
1532 def reportnewinstabilities(repo, tr):
1533 newinstabilitycounts = getinstabilitycounts(repo)
1533 newinstabilitycounts = getinstabilitycounts(repo)
1534 for instability, revset in instabilitytypes:
1534 for instability, revset in instabilitytypes:
1535 delta = (newinstabilitycounts[instability] -
1535 delta = (newinstabilitycounts[instability] -
1536 oldinstabilitycounts[instability])
1536 oldinstabilitycounts[instability])
1537 msg = getinstabilitymessage(delta, instability)
1537 msg = getinstabilitymessage(delta, instability)
1538 if msg:
1538 if msg:
1539 repo.ui.warn(msg)
1539 repo.ui.warn(msg)
1540
1540
1541 if txmatch(_reportnewcssource):
1541 if txmatch(_reportnewcssource):
1542 @reportsummary
1542 @reportsummary
1543 def reportnewcs(repo, tr):
1543 def reportnewcs(repo, tr):
1544 """Report the range of new revisions pulled/unbundled."""
1544 """Report the range of new revisions pulled/unbundled."""
1545 newrevs = tr.changes.get('revs', xrange(0, 0))
1545 newrevs = tr.changes.get('revs', xrange(0, 0))
1546 if not newrevs:
1546 if not newrevs:
1547 return
1547 return
1548
1548
1549 # Compute the bounds of new revisions' range, excluding obsoletes.
1549 # Compute the bounds of new revisions' range, excluding obsoletes.
1550 unfi = repo.unfiltered()
1550 unfi = repo.unfiltered()
1551 revs = unfi.revs('%ld and not obsolete()', newrevs)
1551 revs = unfi.revs('%ld and not obsolete()', newrevs)
1552 if not revs:
1552 if not revs:
1553 # Got only obsoletes.
1553 # Got only obsoletes.
1554 return
1554 return
1555 minrev, maxrev = repo[revs.min()], repo[revs.max()]
1555 minrev, maxrev = repo[revs.min()], repo[revs.max()]
1556
1556
1557 if minrev == maxrev:
1557 if minrev == maxrev:
1558 revrange = minrev
1558 revrange = minrev
1559 else:
1559 else:
1560 revrange = '%s:%s' % (minrev, maxrev)
1560 revrange = '%s:%s' % (minrev, maxrev)
1561 repo.ui.status(_('new changesets %s\n') % revrange)
1561 repo.ui.status(_('new changesets %s\n') % revrange)
1562
1562
1563 @reportsummary
1563 @reportsummary
1564 def reportphasechanges(repo, tr):
1564 def reportphasechanges(repo, tr):
1565 """Report statistics of phase changes for changesets pre-existing
1565 """Report statistics of phase changes for changesets pre-existing
1566 pull/unbundle.
1566 pull/unbundle.
1567 """
1567 """
1568 newrevs = tr.changes.get('revs', xrange(0, 0))
1568 newrevs = tr.changes.get('revs', xrange(0, 0))
1569 phasetracking = tr.changes.get('phases', {})
1569 phasetracking = tr.changes.get('phases', {})
1570 if not phasetracking:
1570 if not phasetracking:
1571 return
1571 return
1572 published = [
1572 published = [
1573 rev for rev, (old, new) in phasetracking.iteritems()
1573 rev for rev, (old, new) in phasetracking.iteritems()
1574 if new == phases.public and rev not in newrevs
1574 if new == phases.public and rev not in newrevs
1575 ]
1575 ]
1576 if not published:
1576 if not published:
1577 return
1577 return
1578 repo.ui.status(_('%d local changesets published\n')
1578 repo.ui.status(_('%d local changesets published\n')
1579 % len(published))
1579 % len(published))
1580
1580
1581 def getinstabilitymessage(delta, instability):
1581 def getinstabilitymessage(delta, instability):
1582 """function to return the message to show warning about new instabilities
1582 """function to return the message to show warning about new instabilities
1583
1583
1584 exists as a separate function so that extension can wrap to show more
1584 exists as a separate function so that extension can wrap to show more
1585 information like how to fix instabilities"""
1585 information like how to fix instabilities"""
1586 if delta > 0:
1586 if delta > 0:
1587 return _('%i new %s changesets\n') % (delta, instability)
1587 return _('%i new %s changesets\n') % (delta, instability)
1588
1588
1589 def nodesummaries(repo, nodes, maxnumnodes=4):
1589 def nodesummaries(repo, nodes, maxnumnodes=4):
1590 if len(nodes) <= maxnumnodes or repo.ui.verbose:
1590 if len(nodes) <= maxnumnodes or repo.ui.verbose:
1591 return ' '.join(short(h) for h in nodes)
1591 return ' '.join(short(h) for h in nodes)
1592 first = ' '.join(short(h) for h in nodes[:maxnumnodes])
1592 first = ' '.join(short(h) for h in nodes[:maxnumnodes])
1593 return _("%s and %d others") % (first, len(nodes) - maxnumnodes)
1593 return _("%s and %d others") % (first, len(nodes) - maxnumnodes)
1594
1594
1595 def enforcesinglehead(repo, tr, desc):
1595 def enforcesinglehead(repo, tr, desc):
1596 """check that no named branch has multiple heads"""
1596 """check that no named branch has multiple heads"""
1597 if desc in ('strip', 'repair'):
1597 if desc in ('strip', 'repair'):
1598 # skip the logic during strip
1598 # skip the logic during strip
1599 return
1599 return
1600 visible = repo.filtered('visible')
1600 visible = repo.filtered('visible')
1601 # possible improvement: we could restrict the check to affected branch
1601 # possible improvement: we could restrict the check to affected branch
1602 for name, heads in visible.branchmap().iteritems():
1602 for name, heads in visible.branchmap().iteritems():
1603 if len(heads) > 1:
1603 if len(heads) > 1:
1604 msg = _('rejecting multiple heads on branch "%s"')
1604 msg = _('rejecting multiple heads on branch "%s"')
1605 msg %= name
1605 msg %= name
1606 hint = _('%d heads: %s')
1606 hint = _('%d heads: %s')
1607 hint %= (len(heads), nodesummaries(repo, heads))
1607 hint %= (len(heads), nodesummaries(repo, heads))
1608 raise error.Abort(msg, hint=hint)
1608 raise error.Abort(msg, hint=hint)
1609
1609
1610 def wrapconvertsink(sink):
1610 def wrapconvertsink(sink):
1611 """Allow extensions to wrap the sink returned by convcmd.convertsink()
1611 """Allow extensions to wrap the sink returned by convcmd.convertsink()
1612 before it is used, whether or not the convert extension was formally loaded.
1612 before it is used, whether or not the convert extension was formally loaded.
1613 """
1613 """
1614 return sink
1614 return sink
1615
1615
1616 def unhidehashlikerevs(repo, specs, hiddentype):
1616 def unhidehashlikerevs(repo, specs, hiddentype):
1617 """parse the user specs and unhide changesets whose hash or revision number
1617 """parse the user specs and unhide changesets whose hash or revision number
1618 is passed.
1618 is passed.
1619
1619
1620 hiddentype can be: 1) 'warn': warn while unhiding changesets
1620 hiddentype can be: 1) 'warn': warn while unhiding changesets
1621 2) 'nowarn': don't warn while unhiding changesets
1621 2) 'nowarn': don't warn while unhiding changesets
1622
1622
1623 returns a repo object with the required changesets unhidden
1623 returns a repo object with the required changesets unhidden
1624 """
1624 """
1625 if not repo.filtername or not repo.ui.configbool('experimental',
1625 if not repo.filtername or not repo.ui.configbool('experimental',
1626 'directaccess'):
1626 'directaccess'):
1627 return repo
1627 return repo
1628
1628
1629 if repo.filtername not in ('visible', 'visible-hidden'):
1629 if repo.filtername not in ('visible', 'visible-hidden'):
1630 return repo
1630 return repo
1631
1631
1632 symbols = set()
1632 symbols = set()
1633 for spec in specs:
1633 for spec in specs:
1634 try:
1634 try:
1635 tree = revsetlang.parse(spec)
1635 tree = revsetlang.parse(spec)
1636 except error.ParseError: # will be reported by scmutil.revrange()
1636 except error.ParseError: # will be reported by scmutil.revrange()
1637 continue
1637 continue
1638
1638
1639 symbols.update(revsetlang.gethashlikesymbols(tree))
1639 symbols.update(revsetlang.gethashlikesymbols(tree))
1640
1640
1641 if not symbols:
1641 if not symbols:
1642 return repo
1642 return repo
1643
1643
1644 revs = _getrevsfromsymbols(repo, symbols)
1644 revs = _getrevsfromsymbols(repo, symbols)
1645
1645
1646 if not revs:
1646 if not revs:
1647 return repo
1647 return repo
1648
1648
1649 if hiddentype == 'warn':
1649 if hiddentype == 'warn':
1650 unfi = repo.unfiltered()
1650 unfi = repo.unfiltered()
1651 revstr = ", ".join([pycompat.bytestr(unfi[l]) for l in revs])
1651 revstr = ", ".join([pycompat.bytestr(unfi[l]) for l in revs])
1652 repo.ui.warn(_("warning: accessing hidden changesets for write "
1652 repo.ui.warn(_("warning: accessing hidden changesets for write "
1653 "operation: %s\n") % revstr)
1653 "operation: %s\n") % revstr)
1654
1654
1655 # we have to use new filtername to separate branch/tags cache until we can
1655 # we have to use new filtername to separate branch/tags cache until we can
1656 # disbale these cache when revisions are dynamically pinned.
1656 # disbale these cache when revisions are dynamically pinned.
1657 return repo.filtered('visible-hidden', revs)
1657 return repo.filtered('visible-hidden', revs)
1658
1658
1659 def _getrevsfromsymbols(repo, symbols):
1659 def _getrevsfromsymbols(repo, symbols):
1660 """parse the list of symbols and returns a set of revision numbers of hidden
1660 """parse the list of symbols and returns a set of revision numbers of hidden
1661 changesets present in symbols"""
1661 changesets present in symbols"""
1662 revs = set()
1662 revs = set()
1663 unfi = repo.unfiltered()
1663 unfi = repo.unfiltered()
1664 unficl = unfi.changelog
1664 unficl = unfi.changelog
1665 cl = repo.changelog
1665 cl = repo.changelog
1666 tiprev = len(unficl)
1666 tiprev = len(unficl)
1667 allowrevnums = repo.ui.configbool('experimental', 'directaccess.revnums')
1667 allowrevnums = repo.ui.configbool('experimental', 'directaccess.revnums')
1668 for s in symbols:
1668 for s in symbols:
1669 try:
1669 try:
1670 n = int(s)
1670 n = int(s)
1671 if n <= tiprev:
1671 if n <= tiprev:
1672 if not allowrevnums:
1672 if not allowrevnums:
1673 continue
1673 continue
1674 else:
1674 else:
1675 if n not in cl:
1675 if n not in cl:
1676 revs.add(n)
1676 revs.add(n)
1677 continue
1677 continue
1678 except ValueError:
1678 except ValueError:
1679 pass
1679 pass
1680
1680
1681 try:
1681 try:
1682 s = resolvehexnodeidprefix(unfi, s)
1682 s = resolvehexnodeidprefix(unfi, s)
1683 except (error.LookupError, error.WdirUnsupported):
1683 except (error.LookupError, error.WdirUnsupported):
1684 s = None
1684 s = None
1685
1685
1686 if s is not None:
1686 if s is not None:
1687 rev = unficl.rev(s)
1687 rev = unficl.rev(s)
1688 if rev not in cl:
1688 if rev not in cl:
1689 revs.add(rev)
1689 revs.add(rev)
1690
1690
1691 return revs
1691 return revs
1692
1692
1693 def bookmarkrevs(repo, mark):
1693 def bookmarkrevs(repo, mark):
1694 """
1694 """
1695 Select revisions reachable by a given bookmark
1695 Select revisions reachable by a given bookmark
1696 """
1696 """
1697 return repo.revs("ancestors(bookmark(%s)) - "
1697 return repo.revs("ancestors(bookmark(%s)) - "
1698 "ancestors(head() and not bookmark(%s)) - "
1698 "ancestors(head() and not bookmark(%s)) - "
1699 "ancestors(bookmark() and not bookmark(%s))",
1699 "ancestors(bookmark() and not bookmark(%s))",
1700 mark, mark, mark)
1700 mark, mark, mark)
@@ -1,1906 +1,1918 b''
1 # ui.py - user interface bits for mercurial
1 # ui.py - user interface bits for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import collections
10 import collections
11 import contextlib
11 import contextlib
12 import errno
12 import errno
13 import getpass
13 import getpass
14 import inspect
14 import inspect
15 import os
15 import os
16 import re
16 import re
17 import signal
17 import signal
18 import socket
18 import socket
19 import subprocess
19 import subprocess
20 import sys
20 import sys
21 import traceback
21 import traceback
22
22
23 from .i18n import _
23 from .i18n import _
24 from .node import hex
24 from .node import hex
25
25
26 from . import (
26 from . import (
27 color,
27 color,
28 config,
28 config,
29 configitems,
29 configitems,
30 encoding,
30 encoding,
31 error,
31 error,
32 formatter,
32 formatter,
33 progress,
33 progress,
34 pycompat,
34 pycompat,
35 rcutil,
35 rcutil,
36 scmutil,
36 scmutil,
37 util,
37 util,
38 )
38 )
39 from .utils import (
39 from .utils import (
40 dateutil,
40 dateutil,
41 procutil,
41 procutil,
42 stringutil,
42 stringutil,
43 )
43 )
44
44
45 urlreq = util.urlreq
45 urlreq = util.urlreq
46
46
47 # for use with str.translate(None, _keepalnum), to keep just alphanumerics
47 # for use with str.translate(None, _keepalnum), to keep just alphanumerics
48 _keepalnum = ''.join(c for c in map(pycompat.bytechr, range(256))
48 _keepalnum = ''.join(c for c in map(pycompat.bytechr, range(256))
49 if not c.isalnum())
49 if not c.isalnum())
50
50
51 # The config knobs that will be altered (if unset) by ui.tweakdefaults.
51 # The config knobs that will be altered (if unset) by ui.tweakdefaults.
52 tweakrc = b"""
52 tweakrc = b"""
53 [ui]
53 [ui]
54 # The rollback command is dangerous. As a rule, don't use it.
54 # The rollback command is dangerous. As a rule, don't use it.
55 rollback = False
55 rollback = False
56 # Make `hg status` report copy information
56 # Make `hg status` report copy information
57 statuscopies = yes
57 statuscopies = yes
58 # Prefer curses UIs when available. Revert to plain-text with `text`.
58 # Prefer curses UIs when available. Revert to plain-text with `text`.
59 interface = curses
59 interface = curses
60
60
61 [commands]
61 [commands]
62 # Grep working directory by default.
62 # Grep working directory by default.
63 grep.all-files = True
63 grep.all-files = True
64 # Make `hg status` emit cwd-relative paths by default.
64 # Make `hg status` emit cwd-relative paths by default.
65 status.relative = yes
65 status.relative = yes
66 # Refuse to perform an `hg update` that would cause a file content merge
66 # Refuse to perform an `hg update` that would cause a file content merge
67 update.check = noconflict
67 update.check = noconflict
68 # Show conflicts information in `hg status`
68 # Show conflicts information in `hg status`
69 status.verbose = True
69 status.verbose = True
70
70
71 [diff]
71 [diff]
72 git = 1
72 git = 1
73 showfunc = 1
73 showfunc = 1
74 word-diff = 1
74 word-diff = 1
75 """
75 """
76
76
77 samplehgrcs = {
77 samplehgrcs = {
78 'user':
78 'user':
79 b"""# example user config (see 'hg help config' for more info)
79 b"""# example user config (see 'hg help config' for more info)
80 [ui]
80 [ui]
81 # name and email, e.g.
81 # name and email, e.g.
82 # username = Jane Doe <jdoe@example.com>
82 # username = Jane Doe <jdoe@example.com>
83 username =
83 username =
84
84
85 # We recommend enabling tweakdefaults to get slight improvements to
85 # We recommend enabling tweakdefaults to get slight improvements to
86 # the UI over time. Make sure to set HGPLAIN in the environment when
86 # the UI over time. Make sure to set HGPLAIN in the environment when
87 # writing scripts!
87 # writing scripts!
88 # tweakdefaults = True
88 # tweakdefaults = True
89
89
90 # uncomment to disable color in command output
90 # uncomment to disable color in command output
91 # (see 'hg help color' for details)
91 # (see 'hg help color' for details)
92 # color = never
92 # color = never
93
93
94 # uncomment to disable command output pagination
94 # uncomment to disable command output pagination
95 # (see 'hg help pager' for details)
95 # (see 'hg help pager' for details)
96 # paginate = never
96 # paginate = never
97
97
98 [extensions]
98 [extensions]
99 # uncomment these lines to enable some popular extensions
99 # uncomment these lines to enable some popular extensions
100 # (see 'hg help extensions' for more info)
100 # (see 'hg help extensions' for more info)
101 #
101 #
102 # churn =
102 # churn =
103 """,
103 """,
104
104
105 'cloned':
105 'cloned':
106 b"""# example repository config (see 'hg help config' for more info)
106 b"""# example repository config (see 'hg help config' for more info)
107 [paths]
107 [paths]
108 default = %s
108 default = %s
109
109
110 # path aliases to other clones of this repo in URLs or filesystem paths
110 # path aliases to other clones of this repo in URLs or filesystem paths
111 # (see 'hg help config.paths' for more info)
111 # (see 'hg help config.paths' for more info)
112 #
112 #
113 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
113 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
114 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
114 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
115 # my-clone = /home/jdoe/jdoes-clone
115 # my-clone = /home/jdoe/jdoes-clone
116
116
117 [ui]
117 [ui]
118 # name and email (local to this repository, optional), e.g.
118 # name and email (local to this repository, optional), e.g.
119 # username = Jane Doe <jdoe@example.com>
119 # username = Jane Doe <jdoe@example.com>
120 """,
120 """,
121
121
122 'local':
122 'local':
123 b"""# example repository config (see 'hg help config' for more info)
123 b"""# example repository config (see 'hg help config' for more info)
124 [paths]
124 [paths]
125 # path aliases to other clones of this repo in URLs or filesystem paths
125 # path aliases to other clones of this repo in URLs or filesystem paths
126 # (see 'hg help config.paths' for more info)
126 # (see 'hg help config.paths' for more info)
127 #
127 #
128 # default = http://example.com/hg/example-repo
128 # default = http://example.com/hg/example-repo
129 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
129 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
130 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
130 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
131 # my-clone = /home/jdoe/jdoes-clone
131 # my-clone = /home/jdoe/jdoes-clone
132
132
133 [ui]
133 [ui]
134 # name and email (local to this repository, optional), e.g.
134 # name and email (local to this repository, optional), e.g.
135 # username = Jane Doe <jdoe@example.com>
135 # username = Jane Doe <jdoe@example.com>
136 """,
136 """,
137
137
138 'global':
138 'global':
139 b"""# example system-wide hg config (see 'hg help config' for more info)
139 b"""# example system-wide hg config (see 'hg help config' for more info)
140
140
141 [ui]
141 [ui]
142 # uncomment to disable color in command output
142 # uncomment to disable color in command output
143 # (see 'hg help color' for details)
143 # (see 'hg help color' for details)
144 # color = never
144 # color = never
145
145
146 # uncomment to disable command output pagination
146 # uncomment to disable command output pagination
147 # (see 'hg help pager' for details)
147 # (see 'hg help pager' for details)
148 # paginate = never
148 # paginate = never
149
149
150 [extensions]
150 [extensions]
151 # uncomment these lines to enable some popular extensions
151 # uncomment these lines to enable some popular extensions
152 # (see 'hg help extensions' for more info)
152 # (see 'hg help extensions' for more info)
153 #
153 #
154 # blackbox =
154 # blackbox =
155 # churn =
155 # churn =
156 """,
156 """,
157 }
157 }
158
158
159 def _maybestrurl(maybebytes):
159 def _maybestrurl(maybebytes):
160 return pycompat.rapply(pycompat.strurl, maybebytes)
160 return pycompat.rapply(pycompat.strurl, maybebytes)
161
161
162 def _maybebytesurl(maybestr):
162 def _maybebytesurl(maybestr):
163 return pycompat.rapply(pycompat.bytesurl, maybestr)
163 return pycompat.rapply(pycompat.bytesurl, maybestr)
164
164
165 class httppasswordmgrdbproxy(object):
165 class httppasswordmgrdbproxy(object):
166 """Delays loading urllib2 until it's needed."""
166 """Delays loading urllib2 until it's needed."""
167 def __init__(self):
167 def __init__(self):
168 self._mgr = None
168 self._mgr = None
169
169
170 def _get_mgr(self):
170 def _get_mgr(self):
171 if self._mgr is None:
171 if self._mgr is None:
172 self._mgr = urlreq.httppasswordmgrwithdefaultrealm()
172 self._mgr = urlreq.httppasswordmgrwithdefaultrealm()
173 return self._mgr
173 return self._mgr
174
174
175 def add_password(self, realm, uris, user, passwd):
175 def add_password(self, realm, uris, user, passwd):
176 return self._get_mgr().add_password(
176 return self._get_mgr().add_password(
177 _maybestrurl(realm), _maybestrurl(uris),
177 _maybestrurl(realm), _maybestrurl(uris),
178 _maybestrurl(user), _maybestrurl(passwd))
178 _maybestrurl(user), _maybestrurl(passwd))
179
179
180 def find_user_password(self, realm, uri):
180 def find_user_password(self, realm, uri):
181 mgr = self._get_mgr()
181 mgr = self._get_mgr()
182 return _maybebytesurl(mgr.find_user_password(_maybestrurl(realm),
182 return _maybebytesurl(mgr.find_user_password(_maybestrurl(realm),
183 _maybestrurl(uri)))
183 _maybestrurl(uri)))
184
184
185 def _catchterm(*args):
185 def _catchterm(*args):
186 raise error.SignalInterrupt
186 raise error.SignalInterrupt
187
187
188 # unique object used to detect no default value has been provided when
188 # unique object used to detect no default value has been provided when
189 # retrieving configuration value.
189 # retrieving configuration value.
190 _unset = object()
190 _unset = object()
191
191
192 # _reqexithandlers: callbacks run at the end of a request
192 # _reqexithandlers: callbacks run at the end of a request
193 _reqexithandlers = []
193 _reqexithandlers = []
194
194
195 class ui(object):
195 class ui(object):
196 def __init__(self, src=None):
196 def __init__(self, src=None):
197 """Create a fresh new ui object if no src given
197 """Create a fresh new ui object if no src given
198
198
199 Use uimod.ui.load() to create a ui which knows global and user configs.
199 Use uimod.ui.load() to create a ui which knows global and user configs.
200 In most cases, you should use ui.copy() to create a copy of an existing
200 In most cases, you should use ui.copy() to create a copy of an existing
201 ui object.
201 ui object.
202 """
202 """
203 # _buffers: used for temporary capture of output
203 # _buffers: used for temporary capture of output
204 self._buffers = []
204 self._buffers = []
205 # 3-tuple describing how each buffer in the stack behaves.
205 # 3-tuple describing how each buffer in the stack behaves.
206 # Values are (capture stderr, capture subprocesses, apply labels).
206 # Values are (capture stderr, capture subprocesses, apply labels).
207 self._bufferstates = []
207 self._bufferstates = []
208 # When a buffer is active, defines whether we are expanding labels.
208 # When a buffer is active, defines whether we are expanding labels.
209 # This exists to prevent an extra list lookup.
209 # This exists to prevent an extra list lookup.
210 self._bufferapplylabels = None
210 self._bufferapplylabels = None
211 self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
211 self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
212 self._reportuntrusted = True
212 self._reportuntrusted = True
213 self._knownconfig = configitems.coreitems
213 self._knownconfig = configitems.coreitems
214 self._ocfg = config.config() # overlay
214 self._ocfg = config.config() # overlay
215 self._tcfg = config.config() # trusted
215 self._tcfg = config.config() # trusted
216 self._ucfg = config.config() # untrusted
216 self._ucfg = config.config() # untrusted
217 self._trustusers = set()
217 self._trustusers = set()
218 self._trustgroups = set()
218 self._trustgroups = set()
219 self.callhooks = True
219 self.callhooks = True
220 # Insecure server connections requested.
220 # Insecure server connections requested.
221 self.insecureconnections = False
221 self.insecureconnections = False
222 # Blocked time
222 # Blocked time
223 self.logblockedtimes = False
223 self.logblockedtimes = False
224 # color mode: see mercurial/color.py for possible value
224 # color mode: see mercurial/color.py for possible value
225 self._colormode = None
225 self._colormode = None
226 self._terminfoparams = {}
226 self._terminfoparams = {}
227 self._styles = {}
227 self._styles = {}
228 self._uninterruptible = False
228 self._uninterruptible = False
229
229
230 if src:
230 if src:
231 self.fout = src.fout
231 self.fout = src.fout
232 self.ferr = src.ferr
232 self.ferr = src.ferr
233 self.fin = src.fin
233 self.fin = src.fin
234 self.pageractive = src.pageractive
234 self.pageractive = src.pageractive
235 self._disablepager = src._disablepager
235 self._disablepager = src._disablepager
236 self._tweaked = src._tweaked
236 self._tweaked = src._tweaked
237
237
238 self._tcfg = src._tcfg.copy()
238 self._tcfg = src._tcfg.copy()
239 self._ucfg = src._ucfg.copy()
239 self._ucfg = src._ucfg.copy()
240 self._ocfg = src._ocfg.copy()
240 self._ocfg = src._ocfg.copy()
241 self._trustusers = src._trustusers.copy()
241 self._trustusers = src._trustusers.copy()
242 self._trustgroups = src._trustgroups.copy()
242 self._trustgroups = src._trustgroups.copy()
243 self.environ = src.environ
243 self.environ = src.environ
244 self.callhooks = src.callhooks
244 self.callhooks = src.callhooks
245 self.insecureconnections = src.insecureconnections
245 self.insecureconnections = src.insecureconnections
246 self._colormode = src._colormode
246 self._colormode = src._colormode
247 self._terminfoparams = src._terminfoparams.copy()
247 self._terminfoparams = src._terminfoparams.copy()
248 self._styles = src._styles.copy()
248 self._styles = src._styles.copy()
249
249
250 self.fixconfig()
250 self.fixconfig()
251
251
252 self.httppasswordmgrdb = src.httppasswordmgrdb
252 self.httppasswordmgrdb = src.httppasswordmgrdb
253 self._blockedtimes = src._blockedtimes
253 self._blockedtimes = src._blockedtimes
254 else:
254 else:
255 self.fout = procutil.stdout
255 self.fout = procutil.stdout
256 self.ferr = procutil.stderr
256 self.ferr = procutil.stderr
257 self.fin = procutil.stdin
257 self.fin = procutil.stdin
258 self.pageractive = False
258 self.pageractive = False
259 self._disablepager = False
259 self._disablepager = False
260 self._tweaked = False
260 self._tweaked = False
261
261
262 # shared read-only environment
262 # shared read-only environment
263 self.environ = encoding.environ
263 self.environ = encoding.environ
264
264
265 self.httppasswordmgrdb = httppasswordmgrdbproxy()
265 self.httppasswordmgrdb = httppasswordmgrdbproxy()
266 self._blockedtimes = collections.defaultdict(int)
266 self._blockedtimes = collections.defaultdict(int)
267
267
268 allowed = self.configlist('experimental', 'exportableenviron')
268 allowed = self.configlist('experimental', 'exportableenviron')
269 if '*' in allowed:
269 if '*' in allowed:
270 self._exportableenviron = self.environ
270 self._exportableenviron = self.environ
271 else:
271 else:
272 self._exportableenviron = {}
272 self._exportableenviron = {}
273 for k in allowed:
273 for k in allowed:
274 if k in self.environ:
274 if k in self.environ:
275 self._exportableenviron[k] = self.environ[k]
275 self._exportableenviron[k] = self.environ[k]
276
276
277 @classmethod
277 @classmethod
278 def load(cls):
278 def load(cls):
279 """Create a ui and load global and user configs"""
279 """Create a ui and load global and user configs"""
280 u = cls()
280 u = cls()
281 # we always trust global config files and environment variables
281 # we always trust global config files and environment variables
282 for t, f in rcutil.rccomponents():
282 for t, f in rcutil.rccomponents():
283 if t == 'path':
283 if t == 'path':
284 u.readconfig(f, trust=True)
284 u.readconfig(f, trust=True)
285 elif t == 'items':
285 elif t == 'items':
286 sections = set()
286 sections = set()
287 for section, name, value, source in f:
287 for section, name, value, source in f:
288 # do not set u._ocfg
288 # do not set u._ocfg
289 # XXX clean this up once immutable config object is a thing
289 # XXX clean this up once immutable config object is a thing
290 u._tcfg.set(section, name, value, source)
290 u._tcfg.set(section, name, value, source)
291 u._ucfg.set(section, name, value, source)
291 u._ucfg.set(section, name, value, source)
292 sections.add(section)
292 sections.add(section)
293 for section in sections:
293 for section in sections:
294 u.fixconfig(section=section)
294 u.fixconfig(section=section)
295 else:
295 else:
296 raise error.ProgrammingError('unknown rctype: %s' % t)
296 raise error.ProgrammingError('unknown rctype: %s' % t)
297 u._maybetweakdefaults()
297 u._maybetweakdefaults()
298 return u
298 return u
299
299
300 def _maybetweakdefaults(self):
300 def _maybetweakdefaults(self):
301 if not self.configbool('ui', 'tweakdefaults'):
301 if not self.configbool('ui', 'tweakdefaults'):
302 return
302 return
303 if self._tweaked or self.plain('tweakdefaults'):
303 if self._tweaked or self.plain('tweakdefaults'):
304 return
304 return
305
305
306 # Note: it is SUPER IMPORTANT that you set self._tweaked to
306 # Note: it is SUPER IMPORTANT that you set self._tweaked to
307 # True *before* any calls to setconfig(), otherwise you'll get
307 # True *before* any calls to setconfig(), otherwise you'll get
308 # infinite recursion between setconfig and this method.
308 # infinite recursion between setconfig and this method.
309 #
309 #
310 # TODO: We should extract an inner method in setconfig() to
310 # TODO: We should extract an inner method in setconfig() to
311 # avoid this weirdness.
311 # avoid this weirdness.
312 self._tweaked = True
312 self._tweaked = True
313 tmpcfg = config.config()
313 tmpcfg = config.config()
314 tmpcfg.parse('<tweakdefaults>', tweakrc)
314 tmpcfg.parse('<tweakdefaults>', tweakrc)
315 for section in tmpcfg:
315 for section in tmpcfg:
316 for name, value in tmpcfg.items(section):
316 for name, value in tmpcfg.items(section):
317 if not self.hasconfig(section, name):
317 if not self.hasconfig(section, name):
318 self.setconfig(section, name, value, "<tweakdefaults>")
318 self.setconfig(section, name, value, "<tweakdefaults>")
319
319
320 def copy(self):
320 def copy(self):
321 return self.__class__(self)
321 return self.__class__(self)
322
322
323 def resetstate(self):
323 def resetstate(self):
324 """Clear internal state that shouldn't persist across commands"""
324 """Clear internal state that shouldn't persist across commands"""
325 if self._progbar:
325 if self._progbar:
326 self._progbar.resetstate() # reset last-print time of progress bar
326 self._progbar.resetstate() # reset last-print time of progress bar
327 self.httppasswordmgrdb = httppasswordmgrdbproxy()
327 self.httppasswordmgrdb = httppasswordmgrdbproxy()
328
328
329 @contextlib.contextmanager
329 @contextlib.contextmanager
330 def timeblockedsection(self, key):
330 def timeblockedsection(self, key):
331 # this is open-coded below - search for timeblockedsection to find them
331 # this is open-coded below - search for timeblockedsection to find them
332 starttime = util.timer()
332 starttime = util.timer()
333 try:
333 try:
334 yield
334 yield
335 finally:
335 finally:
336 self._blockedtimes[key + '_blocked'] += \
336 self._blockedtimes[key + '_blocked'] += \
337 (util.timer() - starttime) * 1000
337 (util.timer() - starttime) * 1000
338
338
339 @contextlib.contextmanager
339 @contextlib.contextmanager
340 def uninterruptable(self):
340 def uninterruptable(self):
341 """Mark an operation as unsafe.
341 """Mark an operation as unsafe.
342
342
343 Most operations on a repository are safe to interrupt, but a
343 Most operations on a repository are safe to interrupt, but a
344 few are risky (for example repair.strip). This context manager
344 few are risky (for example repair.strip). This context manager
345 lets you advise Mercurial that something risky is happening so
345 lets you advise Mercurial that something risky is happening so
346 that control-C etc can be blocked if desired.
346 that control-C etc can be blocked if desired.
347 """
347 """
348 enabled = self.configbool('experimental', 'nointerrupt')
348 enabled = self.configbool('experimental', 'nointerrupt')
349 if (enabled and
349 if (enabled and
350 self.configbool('experimental', 'nointerrupt-interactiveonly')):
350 self.configbool('experimental', 'nointerrupt-interactiveonly')):
351 enabled = self.interactive()
351 enabled = self.interactive()
352 if self._uninterruptible or not enabled:
352 if self._uninterruptible or not enabled:
353 # if nointerrupt support is turned off, the process isn't
353 # if nointerrupt support is turned off, the process isn't
354 # interactive, or we're already in an uninterruptable
354 # interactive, or we're already in an uninterruptable
355 # block, do nothing.
355 # block, do nothing.
356 yield
356 yield
357 return
357 return
358 def warn():
358 def warn():
359 self.warn(_("shutting down cleanly\n"))
359 self.warn(_("shutting down cleanly\n"))
360 self.warn(
360 self.warn(
361 _("press ^C again to terminate immediately (dangerous)\n"))
361 _("press ^C again to terminate immediately (dangerous)\n"))
362 return True
362 return True
363 with procutil.uninterruptable(warn):
363 with procutil.uninterruptable(warn):
364 try:
364 try:
365 self._uninterruptible = True
365 self._uninterruptible = True
366 yield
366 yield
367 finally:
367 finally:
368 self._uninterruptible = False
368 self._uninterruptible = False
369
369
370 def formatter(self, topic, opts):
370 def formatter(self, topic, opts):
371 return formatter.formatter(self, self, topic, opts)
371 return formatter.formatter(self, self, topic, opts)
372
372
373 def _trusted(self, fp, f):
373 def _trusted(self, fp, f):
374 st = util.fstat(fp)
374 st = util.fstat(fp)
375 if util.isowner(st):
375 if util.isowner(st):
376 return True
376 return True
377
377
378 tusers, tgroups = self._trustusers, self._trustgroups
378 tusers, tgroups = self._trustusers, self._trustgroups
379 if '*' in tusers or '*' in tgroups:
379 if '*' in tusers or '*' in tgroups:
380 return True
380 return True
381
381
382 user = util.username(st.st_uid)
382 user = util.username(st.st_uid)
383 group = util.groupname(st.st_gid)
383 group = util.groupname(st.st_gid)
384 if user in tusers or group in tgroups or user == util.username():
384 if user in tusers or group in tgroups or user == util.username():
385 return True
385 return True
386
386
387 if self._reportuntrusted:
387 if self._reportuntrusted:
388 self.warn(_('not trusting file %s from untrusted '
388 self.warn(_('not trusting file %s from untrusted '
389 'user %s, group %s\n') % (f, user, group))
389 'user %s, group %s\n') % (f, user, group))
390 return False
390 return False
391
391
392 def readconfig(self, filename, root=None, trust=False,
392 def readconfig(self, filename, root=None, trust=False,
393 sections=None, remap=None):
393 sections=None, remap=None):
394 try:
394 try:
395 fp = open(filename, r'rb')
395 fp = open(filename, r'rb')
396 except IOError:
396 except IOError:
397 if not sections: # ignore unless we were looking for something
397 if not sections: # ignore unless we were looking for something
398 return
398 return
399 raise
399 raise
400
400
401 cfg = config.config()
401 cfg = config.config()
402 trusted = sections or trust or self._trusted(fp, filename)
402 trusted = sections or trust or self._trusted(fp, filename)
403
403
404 try:
404 try:
405 cfg.read(filename, fp, sections=sections, remap=remap)
405 cfg.read(filename, fp, sections=sections, remap=remap)
406 fp.close()
406 fp.close()
407 except error.ConfigError as inst:
407 except error.ConfigError as inst:
408 if trusted:
408 if trusted:
409 raise
409 raise
410 self.warn(_("ignored: %s\n") % stringutil.forcebytestr(inst))
410 self.warn(_("ignored: %s\n") % stringutil.forcebytestr(inst))
411
411
412 if self.plain():
412 if self.plain():
413 for k in ('debug', 'fallbackencoding', 'quiet', 'slash',
413 for k in ('debug', 'fallbackencoding', 'quiet', 'slash',
414 'logtemplate', 'statuscopies', 'style',
414 'logtemplate', 'statuscopies', 'style',
415 'traceback', 'verbose'):
415 'traceback', 'verbose'):
416 if k in cfg['ui']:
416 if k in cfg['ui']:
417 del cfg['ui'][k]
417 del cfg['ui'][k]
418 for k, v in cfg.items('defaults'):
418 for k, v in cfg.items('defaults'):
419 del cfg['defaults'][k]
419 del cfg['defaults'][k]
420 for k, v in cfg.items('commands'):
420 for k, v in cfg.items('commands'):
421 del cfg['commands'][k]
421 del cfg['commands'][k]
422 # Don't remove aliases from the configuration if in the exceptionlist
422 # Don't remove aliases from the configuration if in the exceptionlist
423 if self.plain('alias'):
423 if self.plain('alias'):
424 for k, v in cfg.items('alias'):
424 for k, v in cfg.items('alias'):
425 del cfg['alias'][k]
425 del cfg['alias'][k]
426 if self.plain('revsetalias'):
426 if self.plain('revsetalias'):
427 for k, v in cfg.items('revsetalias'):
427 for k, v in cfg.items('revsetalias'):
428 del cfg['revsetalias'][k]
428 del cfg['revsetalias'][k]
429 if self.plain('templatealias'):
429 if self.plain('templatealias'):
430 for k, v in cfg.items('templatealias'):
430 for k, v in cfg.items('templatealias'):
431 del cfg['templatealias'][k]
431 del cfg['templatealias'][k]
432
432
433 if trusted:
433 if trusted:
434 self._tcfg.update(cfg)
434 self._tcfg.update(cfg)
435 self._tcfg.update(self._ocfg)
435 self._tcfg.update(self._ocfg)
436 self._ucfg.update(cfg)
436 self._ucfg.update(cfg)
437 self._ucfg.update(self._ocfg)
437 self._ucfg.update(self._ocfg)
438
438
439 if root is None:
439 if root is None:
440 root = os.path.expanduser('~')
440 root = os.path.expanduser('~')
441 self.fixconfig(root=root)
441 self.fixconfig(root=root)
442
442
443 def fixconfig(self, root=None, section=None):
443 def fixconfig(self, root=None, section=None):
444 if section in (None, 'paths'):
444 if section in (None, 'paths'):
445 # expand vars and ~
445 # expand vars and ~
446 # translate paths relative to root (or home) into absolute paths
446 # translate paths relative to root (or home) into absolute paths
447 root = root or pycompat.getcwd()
447 root = root or pycompat.getcwd()
448 for c in self._tcfg, self._ucfg, self._ocfg:
448 for c in self._tcfg, self._ucfg, self._ocfg:
449 for n, p in c.items('paths'):
449 for n, p in c.items('paths'):
450 # Ignore sub-options.
450 # Ignore sub-options.
451 if ':' in n:
451 if ':' in n:
452 continue
452 continue
453 if not p:
453 if not p:
454 continue
454 continue
455 if '%%' in p:
455 if '%%' in p:
456 s = self.configsource('paths', n) or 'none'
456 s = self.configsource('paths', n) or 'none'
457 self.warn(_("(deprecated '%%' in path %s=%s from %s)\n")
457 self.warn(_("(deprecated '%%' in path %s=%s from %s)\n")
458 % (n, p, s))
458 % (n, p, s))
459 p = p.replace('%%', '%')
459 p = p.replace('%%', '%')
460 p = util.expandpath(p)
460 p = util.expandpath(p)
461 if not util.hasscheme(p) and not os.path.isabs(p):
461 if not util.hasscheme(p) and not os.path.isabs(p):
462 p = os.path.normpath(os.path.join(root, p))
462 p = os.path.normpath(os.path.join(root, p))
463 c.set("paths", n, p)
463 c.set("paths", n, p)
464
464
465 if section in (None, 'ui'):
465 if section in (None, 'ui'):
466 # update ui options
466 # update ui options
467 self.debugflag = self.configbool('ui', 'debug')
467 self.debugflag = self.configbool('ui', 'debug')
468 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
468 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
469 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
469 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
470 if self.verbose and self.quiet:
470 if self.verbose and self.quiet:
471 self.quiet = self.verbose = False
471 self.quiet = self.verbose = False
472 self._reportuntrusted = self.debugflag or self.configbool("ui",
472 self._reportuntrusted = self.debugflag or self.configbool("ui",
473 "report_untrusted")
473 "report_untrusted")
474 self.tracebackflag = self.configbool('ui', 'traceback')
474 self.tracebackflag = self.configbool('ui', 'traceback')
475 self.logblockedtimes = self.configbool('ui', 'logblockedtimes')
475 self.logblockedtimes = self.configbool('ui', 'logblockedtimes')
476
476
477 if section in (None, 'trusted'):
477 if section in (None, 'trusted'):
478 # update trust information
478 # update trust information
479 self._trustusers.update(self.configlist('trusted', 'users'))
479 self._trustusers.update(self.configlist('trusted', 'users'))
480 self._trustgroups.update(self.configlist('trusted', 'groups'))
480 self._trustgroups.update(self.configlist('trusted', 'groups'))
481
481
482 def backupconfig(self, section, item):
482 def backupconfig(self, section, item):
483 return (self._ocfg.backup(section, item),
483 return (self._ocfg.backup(section, item),
484 self._tcfg.backup(section, item),
484 self._tcfg.backup(section, item),
485 self._ucfg.backup(section, item),)
485 self._ucfg.backup(section, item),)
486 def restoreconfig(self, data):
486 def restoreconfig(self, data):
487 self._ocfg.restore(data[0])
487 self._ocfg.restore(data[0])
488 self._tcfg.restore(data[1])
488 self._tcfg.restore(data[1])
489 self._ucfg.restore(data[2])
489 self._ucfg.restore(data[2])
490
490
491 def setconfig(self, section, name, value, source=''):
491 def setconfig(self, section, name, value, source=''):
492 for cfg in (self._ocfg, self._tcfg, self._ucfg):
492 for cfg in (self._ocfg, self._tcfg, self._ucfg):
493 cfg.set(section, name, value, source)
493 cfg.set(section, name, value, source)
494 self.fixconfig(section=section)
494 self.fixconfig(section=section)
495 self._maybetweakdefaults()
495 self._maybetweakdefaults()
496
496
497 def _data(self, untrusted):
497 def _data(self, untrusted):
498 return untrusted and self._ucfg or self._tcfg
498 return untrusted and self._ucfg or self._tcfg
499
499
500 def configsource(self, section, name, untrusted=False):
500 def configsource(self, section, name, untrusted=False):
501 return self._data(untrusted).source(section, name)
501 return self._data(untrusted).source(section, name)
502
502
503 def config(self, section, name, default=_unset, untrusted=False):
503 def config(self, section, name, default=_unset, untrusted=False):
504 """return the plain string version of a config"""
504 """return the plain string version of a config"""
505 value = self._config(section, name, default=default,
505 value = self._config(section, name, default=default,
506 untrusted=untrusted)
506 untrusted=untrusted)
507 if value is _unset:
507 if value is _unset:
508 return None
508 return None
509 return value
509 return value
510
510
511 def _config(self, section, name, default=_unset, untrusted=False):
511 def _config(self, section, name, default=_unset, untrusted=False):
512 value = itemdefault = default
512 value = itemdefault = default
513 item = self._knownconfig.get(section, {}).get(name)
513 item = self._knownconfig.get(section, {}).get(name)
514 alternates = [(section, name)]
514 alternates = [(section, name)]
515
515
516 if item is not None:
516 if item is not None:
517 alternates.extend(item.alias)
517 alternates.extend(item.alias)
518 if callable(item.default):
518 if callable(item.default):
519 itemdefault = item.default()
519 itemdefault = item.default()
520 else:
520 else:
521 itemdefault = item.default
521 itemdefault = item.default
522 else:
522 else:
523 msg = ("accessing unregistered config item: '%s.%s'")
523 msg = ("accessing unregistered config item: '%s.%s'")
524 msg %= (section, name)
524 msg %= (section, name)
525 self.develwarn(msg, 2, 'warn-config-unknown')
525 self.develwarn(msg, 2, 'warn-config-unknown')
526
526
527 if default is _unset:
527 if default is _unset:
528 if item is None:
528 if item is None:
529 value = default
529 value = default
530 elif item.default is configitems.dynamicdefault:
530 elif item.default is configitems.dynamicdefault:
531 value = None
531 value = None
532 msg = "config item requires an explicit default value: '%s.%s'"
532 msg = "config item requires an explicit default value: '%s.%s'"
533 msg %= (section, name)
533 msg %= (section, name)
534 self.develwarn(msg, 2, 'warn-config-default')
534 self.develwarn(msg, 2, 'warn-config-default')
535 else:
535 else:
536 value = itemdefault
536 value = itemdefault
537 elif (item is not None
537 elif (item is not None
538 and item.default is not configitems.dynamicdefault
538 and item.default is not configitems.dynamicdefault
539 and default != itemdefault):
539 and default != itemdefault):
540 msg = ("specifying a mismatched default value for a registered "
540 msg = ("specifying a mismatched default value for a registered "
541 "config item: '%s.%s' '%s'")
541 "config item: '%s.%s' '%s'")
542 msg %= (section, name, pycompat.bytestr(default))
542 msg %= (section, name, pycompat.bytestr(default))
543 self.develwarn(msg, 2, 'warn-config-default')
543 self.develwarn(msg, 2, 'warn-config-default')
544
544
545 for s, n in alternates:
545 for s, n in alternates:
546 candidate = self._data(untrusted).get(s, n, None)
546 candidate = self._data(untrusted).get(s, n, None)
547 if candidate is not None:
547 if candidate is not None:
548 value = candidate
548 value = candidate
549 section = s
549 section = s
550 name = n
550 name = n
551 break
551 break
552
552
553 if self.debugflag and not untrusted and self._reportuntrusted:
553 if self.debugflag and not untrusted and self._reportuntrusted:
554 for s, n in alternates:
554 for s, n in alternates:
555 uvalue = self._ucfg.get(s, n)
555 uvalue = self._ucfg.get(s, n)
556 if uvalue is not None and uvalue != value:
556 if uvalue is not None and uvalue != value:
557 self.debug("ignoring untrusted configuration option "
557 self.debug("ignoring untrusted configuration option "
558 "%s.%s = %s\n" % (s, n, uvalue))
558 "%s.%s = %s\n" % (s, n, uvalue))
559 return value
559 return value
560
560
561 def configsuboptions(self, section, name, default=_unset, untrusted=False):
561 def configsuboptions(self, section, name, default=_unset, untrusted=False):
562 """Get a config option and all sub-options.
562 """Get a config option and all sub-options.
563
563
564 Some config options have sub-options that are declared with the
564 Some config options have sub-options that are declared with the
565 format "key:opt = value". This method is used to return the main
565 format "key:opt = value". This method is used to return the main
566 option and all its declared sub-options.
566 option and all its declared sub-options.
567
567
568 Returns a 2-tuple of ``(option, sub-options)``, where `sub-options``
568 Returns a 2-tuple of ``(option, sub-options)``, where `sub-options``
569 is a dict of defined sub-options where keys and values are strings.
569 is a dict of defined sub-options where keys and values are strings.
570 """
570 """
571 main = self.config(section, name, default, untrusted=untrusted)
571 main = self.config(section, name, default, untrusted=untrusted)
572 data = self._data(untrusted)
572 data = self._data(untrusted)
573 sub = {}
573 sub = {}
574 prefix = '%s:' % name
574 prefix = '%s:' % name
575 for k, v in data.items(section):
575 for k, v in data.items(section):
576 if k.startswith(prefix):
576 if k.startswith(prefix):
577 sub[k[len(prefix):]] = v
577 sub[k[len(prefix):]] = v
578
578
579 if self.debugflag and not untrusted and self._reportuntrusted:
579 if self.debugflag and not untrusted and self._reportuntrusted:
580 for k, v in sub.items():
580 for k, v in sub.items():
581 uvalue = self._ucfg.get(section, '%s:%s' % (name, k))
581 uvalue = self._ucfg.get(section, '%s:%s' % (name, k))
582 if uvalue is not None and uvalue != v:
582 if uvalue is not None and uvalue != v:
583 self.debug('ignoring untrusted configuration option '
583 self.debug('ignoring untrusted configuration option '
584 '%s:%s.%s = %s\n' % (section, name, k, uvalue))
584 '%s:%s.%s = %s\n' % (section, name, k, uvalue))
585
585
586 return main, sub
586 return main, sub
587
587
588 def configpath(self, section, name, default=_unset, untrusted=False):
588 def configpath(self, section, name, default=_unset, untrusted=False):
589 'get a path config item, expanded relative to repo root or config file'
589 'get a path config item, expanded relative to repo root or config file'
590 v = self.config(section, name, default, untrusted)
590 v = self.config(section, name, default, untrusted)
591 if v is None:
591 if v is None:
592 return None
592 return None
593 if not os.path.isabs(v) or "://" not in v:
593 if not os.path.isabs(v) or "://" not in v:
594 src = self.configsource(section, name, untrusted)
594 src = self.configsource(section, name, untrusted)
595 if ':' in src:
595 if ':' in src:
596 base = os.path.dirname(src.rsplit(':')[0])
596 base = os.path.dirname(src.rsplit(':')[0])
597 v = os.path.join(base, os.path.expanduser(v))
597 v = os.path.join(base, os.path.expanduser(v))
598 return v
598 return v
599
599
600 def configbool(self, section, name, default=_unset, untrusted=False):
600 def configbool(self, section, name, default=_unset, untrusted=False):
601 """parse a configuration element as a boolean
601 """parse a configuration element as a boolean
602
602
603 >>> u = ui(); s = b'foo'
603 >>> u = ui(); s = b'foo'
604 >>> u.setconfig(s, b'true', b'yes')
604 >>> u.setconfig(s, b'true', b'yes')
605 >>> u.configbool(s, b'true')
605 >>> u.configbool(s, b'true')
606 True
606 True
607 >>> u.setconfig(s, b'false', b'no')
607 >>> u.setconfig(s, b'false', b'no')
608 >>> u.configbool(s, b'false')
608 >>> u.configbool(s, b'false')
609 False
609 False
610 >>> u.configbool(s, b'unknown')
610 >>> u.configbool(s, b'unknown')
611 False
611 False
612 >>> u.configbool(s, b'unknown', True)
612 >>> u.configbool(s, b'unknown', True)
613 True
613 True
614 >>> u.setconfig(s, b'invalid', b'somevalue')
614 >>> u.setconfig(s, b'invalid', b'somevalue')
615 >>> u.configbool(s, b'invalid')
615 >>> u.configbool(s, b'invalid')
616 Traceback (most recent call last):
616 Traceback (most recent call last):
617 ...
617 ...
618 ConfigError: foo.invalid is not a boolean ('somevalue')
618 ConfigError: foo.invalid is not a boolean ('somevalue')
619 """
619 """
620
620
621 v = self._config(section, name, default, untrusted=untrusted)
621 v = self._config(section, name, default, untrusted=untrusted)
622 if v is None:
622 if v is None:
623 return v
623 return v
624 if v is _unset:
624 if v is _unset:
625 if default is _unset:
625 if default is _unset:
626 return False
626 return False
627 return default
627 return default
628 if isinstance(v, bool):
628 if isinstance(v, bool):
629 return v
629 return v
630 b = stringutil.parsebool(v)
630 b = stringutil.parsebool(v)
631 if b is None:
631 if b is None:
632 raise error.ConfigError(_("%s.%s is not a boolean ('%s')")
632 raise error.ConfigError(_("%s.%s is not a boolean ('%s')")
633 % (section, name, v))
633 % (section, name, v))
634 return b
634 return b
635
635
636 def configwith(self, convert, section, name, default=_unset,
636 def configwith(self, convert, section, name, default=_unset,
637 desc=None, untrusted=False):
637 desc=None, untrusted=False):
638 """parse a configuration element with a conversion function
638 """parse a configuration element with a conversion function
639
639
640 >>> u = ui(); s = b'foo'
640 >>> u = ui(); s = b'foo'
641 >>> u.setconfig(s, b'float1', b'42')
641 >>> u.setconfig(s, b'float1', b'42')
642 >>> u.configwith(float, s, b'float1')
642 >>> u.configwith(float, s, b'float1')
643 42.0
643 42.0
644 >>> u.setconfig(s, b'float2', b'-4.25')
644 >>> u.setconfig(s, b'float2', b'-4.25')
645 >>> u.configwith(float, s, b'float2')
645 >>> u.configwith(float, s, b'float2')
646 -4.25
646 -4.25
647 >>> u.configwith(float, s, b'unknown', 7)
647 >>> u.configwith(float, s, b'unknown', 7)
648 7.0
648 7.0
649 >>> u.setconfig(s, b'invalid', b'somevalue')
649 >>> u.setconfig(s, b'invalid', b'somevalue')
650 >>> u.configwith(float, s, b'invalid')
650 >>> u.configwith(float, s, b'invalid')
651 Traceback (most recent call last):
651 Traceback (most recent call last):
652 ...
652 ...
653 ConfigError: foo.invalid is not a valid float ('somevalue')
653 ConfigError: foo.invalid is not a valid float ('somevalue')
654 >>> u.configwith(float, s, b'invalid', desc=b'womble')
654 >>> u.configwith(float, s, b'invalid', desc=b'womble')
655 Traceback (most recent call last):
655 Traceback (most recent call last):
656 ...
656 ...
657 ConfigError: foo.invalid is not a valid womble ('somevalue')
657 ConfigError: foo.invalid is not a valid womble ('somevalue')
658 """
658 """
659
659
660 v = self.config(section, name, default, untrusted)
660 v = self.config(section, name, default, untrusted)
661 if v is None:
661 if v is None:
662 return v # do not attempt to convert None
662 return v # do not attempt to convert None
663 try:
663 try:
664 return convert(v)
664 return convert(v)
665 except (ValueError, error.ParseError):
665 except (ValueError, error.ParseError):
666 if desc is None:
666 if desc is None:
667 desc = pycompat.sysbytes(convert.__name__)
667 desc = pycompat.sysbytes(convert.__name__)
668 raise error.ConfigError(_("%s.%s is not a valid %s ('%s')")
668 raise error.ConfigError(_("%s.%s is not a valid %s ('%s')")
669 % (section, name, desc, v))
669 % (section, name, desc, v))
670
670
671 def configint(self, section, name, default=_unset, untrusted=False):
671 def configint(self, section, name, default=_unset, untrusted=False):
672 """parse a configuration element as an integer
672 """parse a configuration element as an integer
673
673
674 >>> u = ui(); s = b'foo'
674 >>> u = ui(); s = b'foo'
675 >>> u.setconfig(s, b'int1', b'42')
675 >>> u.setconfig(s, b'int1', b'42')
676 >>> u.configint(s, b'int1')
676 >>> u.configint(s, b'int1')
677 42
677 42
678 >>> u.setconfig(s, b'int2', b'-42')
678 >>> u.setconfig(s, b'int2', b'-42')
679 >>> u.configint(s, b'int2')
679 >>> u.configint(s, b'int2')
680 -42
680 -42
681 >>> u.configint(s, b'unknown', 7)
681 >>> u.configint(s, b'unknown', 7)
682 7
682 7
683 >>> u.setconfig(s, b'invalid', b'somevalue')
683 >>> u.setconfig(s, b'invalid', b'somevalue')
684 >>> u.configint(s, b'invalid')
684 >>> u.configint(s, b'invalid')
685 Traceback (most recent call last):
685 Traceback (most recent call last):
686 ...
686 ...
687 ConfigError: foo.invalid is not a valid integer ('somevalue')
687 ConfigError: foo.invalid is not a valid integer ('somevalue')
688 """
688 """
689
689
690 return self.configwith(int, section, name, default, 'integer',
690 return self.configwith(int, section, name, default, 'integer',
691 untrusted)
691 untrusted)
692
692
693 def configbytes(self, section, name, default=_unset, untrusted=False):
693 def configbytes(self, section, name, default=_unset, untrusted=False):
694 """parse a configuration element as a quantity in bytes
694 """parse a configuration element as a quantity in bytes
695
695
696 Units can be specified as b (bytes), k or kb (kilobytes), m or
696 Units can be specified as b (bytes), k or kb (kilobytes), m or
697 mb (megabytes), g or gb (gigabytes).
697 mb (megabytes), g or gb (gigabytes).
698
698
699 >>> u = ui(); s = b'foo'
699 >>> u = ui(); s = b'foo'
700 >>> u.setconfig(s, b'val1', b'42')
700 >>> u.setconfig(s, b'val1', b'42')
701 >>> u.configbytes(s, b'val1')
701 >>> u.configbytes(s, b'val1')
702 42
702 42
703 >>> u.setconfig(s, b'val2', b'42.5 kb')
703 >>> u.setconfig(s, b'val2', b'42.5 kb')
704 >>> u.configbytes(s, b'val2')
704 >>> u.configbytes(s, b'val2')
705 43520
705 43520
706 >>> u.configbytes(s, b'unknown', b'7 MB')
706 >>> u.configbytes(s, b'unknown', b'7 MB')
707 7340032
707 7340032
708 >>> u.setconfig(s, b'invalid', b'somevalue')
708 >>> u.setconfig(s, b'invalid', b'somevalue')
709 >>> u.configbytes(s, b'invalid')
709 >>> u.configbytes(s, b'invalid')
710 Traceback (most recent call last):
710 Traceback (most recent call last):
711 ...
711 ...
712 ConfigError: foo.invalid is not a byte quantity ('somevalue')
712 ConfigError: foo.invalid is not a byte quantity ('somevalue')
713 """
713 """
714
714
715 value = self._config(section, name, default, untrusted)
715 value = self._config(section, name, default, untrusted)
716 if value is _unset:
716 if value is _unset:
717 if default is _unset:
717 if default is _unset:
718 default = 0
718 default = 0
719 value = default
719 value = default
720 if not isinstance(value, bytes):
720 if not isinstance(value, bytes):
721 return value
721 return value
722 try:
722 try:
723 return util.sizetoint(value)
723 return util.sizetoint(value)
724 except error.ParseError:
724 except error.ParseError:
725 raise error.ConfigError(_("%s.%s is not a byte quantity ('%s')")
725 raise error.ConfigError(_("%s.%s is not a byte quantity ('%s')")
726 % (section, name, value))
726 % (section, name, value))
727
727
728 def configlist(self, section, name, default=_unset, untrusted=False):
728 def configlist(self, section, name, default=_unset, untrusted=False):
729 """parse a configuration element as a list of comma/space separated
729 """parse a configuration element as a list of comma/space separated
730 strings
730 strings
731
731
732 >>> u = ui(); s = b'foo'
732 >>> u = ui(); s = b'foo'
733 >>> u.setconfig(s, b'list1', b'this,is "a small" ,test')
733 >>> u.setconfig(s, b'list1', b'this,is "a small" ,test')
734 >>> u.configlist(s, b'list1')
734 >>> u.configlist(s, b'list1')
735 ['this', 'is', 'a small', 'test']
735 ['this', 'is', 'a small', 'test']
736 >>> u.setconfig(s, b'list2', b'this, is "a small" , test ')
736 >>> u.setconfig(s, b'list2', b'this, is "a small" , test ')
737 >>> u.configlist(s, b'list2')
737 >>> u.configlist(s, b'list2')
738 ['this', 'is', 'a small', 'test']
738 ['this', 'is', 'a small', 'test']
739 """
739 """
740 # default is not always a list
740 # default is not always a list
741 v = self.configwith(config.parselist, section, name, default,
741 v = self.configwith(config.parselist, section, name, default,
742 'list', untrusted)
742 'list', untrusted)
743 if isinstance(v, bytes):
743 if isinstance(v, bytes):
744 return config.parselist(v)
744 return config.parselist(v)
745 elif v is None:
745 elif v is None:
746 return []
746 return []
747 return v
747 return v
748
748
749 def configdate(self, section, name, default=_unset, untrusted=False):
749 def configdate(self, section, name, default=_unset, untrusted=False):
750 """parse a configuration element as a tuple of ints
750 """parse a configuration element as a tuple of ints
751
751
752 >>> u = ui(); s = b'foo'
752 >>> u = ui(); s = b'foo'
753 >>> u.setconfig(s, b'date', b'0 0')
753 >>> u.setconfig(s, b'date', b'0 0')
754 >>> u.configdate(s, b'date')
754 >>> u.configdate(s, b'date')
755 (0, 0)
755 (0, 0)
756 """
756 """
757 if self.config(section, name, default, untrusted):
757 if self.config(section, name, default, untrusted):
758 return self.configwith(dateutil.parsedate, section, name, default,
758 return self.configwith(dateutil.parsedate, section, name, default,
759 'date', untrusted)
759 'date', untrusted)
760 if default is _unset:
760 if default is _unset:
761 return None
761 return None
762 return default
762 return default
763
763
764 def hasconfig(self, section, name, untrusted=False):
764 def hasconfig(self, section, name, untrusted=False):
765 return self._data(untrusted).hasitem(section, name)
765 return self._data(untrusted).hasitem(section, name)
766
766
767 def has_section(self, section, untrusted=False):
767 def has_section(self, section, untrusted=False):
768 '''tell whether section exists in config.'''
768 '''tell whether section exists in config.'''
769 return section in self._data(untrusted)
769 return section in self._data(untrusted)
770
770
771 def configitems(self, section, untrusted=False, ignoresub=False):
771 def configitems(self, section, untrusted=False, ignoresub=False):
772 items = self._data(untrusted).items(section)
772 items = self._data(untrusted).items(section)
773 if ignoresub:
773 if ignoresub:
774 items = [i for i in items if ':' not in i[0]]
774 items = [i for i in items if ':' not in i[0]]
775 if self.debugflag and not untrusted and self._reportuntrusted:
775 if self.debugflag and not untrusted and self._reportuntrusted:
776 for k, v in self._ucfg.items(section):
776 for k, v in self._ucfg.items(section):
777 if self._tcfg.get(section, k) != v:
777 if self._tcfg.get(section, k) != v:
778 self.debug("ignoring untrusted configuration option "
778 self.debug("ignoring untrusted configuration option "
779 "%s.%s = %s\n" % (section, k, v))
779 "%s.%s = %s\n" % (section, k, v))
780 return items
780 return items
781
781
782 def walkconfig(self, untrusted=False):
782 def walkconfig(self, untrusted=False):
783 cfg = self._data(untrusted)
783 cfg = self._data(untrusted)
784 for section in cfg.sections():
784 for section in cfg.sections():
785 for name, value in self.configitems(section, untrusted):
785 for name, value in self.configitems(section, untrusted):
786 yield section, name, value
786 yield section, name, value
787
787
788 def plain(self, feature=None):
788 def plain(self, feature=None):
789 '''is plain mode active?
789 '''is plain mode active?
790
790
791 Plain mode means that all configuration variables which affect
791 Plain mode means that all configuration variables which affect
792 the behavior and output of Mercurial should be
792 the behavior and output of Mercurial should be
793 ignored. Additionally, the output should be stable,
793 ignored. Additionally, the output should be stable,
794 reproducible and suitable for use in scripts or applications.
794 reproducible and suitable for use in scripts or applications.
795
795
796 The only way to trigger plain mode is by setting either the
796 The only way to trigger plain mode is by setting either the
797 `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
797 `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
798
798
799 The return value can either be
799 The return value can either be
800 - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
800 - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
801 - False if feature is disabled by default and not included in HGPLAIN
801 - False if feature is disabled by default and not included in HGPLAIN
802 - True otherwise
802 - True otherwise
803 '''
803 '''
804 if ('HGPLAIN' not in encoding.environ and
804 if ('HGPLAIN' not in encoding.environ and
805 'HGPLAINEXCEPT' not in encoding.environ):
805 'HGPLAINEXCEPT' not in encoding.environ):
806 return False
806 return False
807 exceptions = encoding.environ.get('HGPLAINEXCEPT',
807 exceptions = encoding.environ.get('HGPLAINEXCEPT',
808 '').strip().split(',')
808 '').strip().split(',')
809 # TODO: add support for HGPLAIN=+feature,-feature syntax
809 # TODO: add support for HGPLAIN=+feature,-feature syntax
810 if '+strictflags' not in encoding.environ.get('HGPLAIN', '').split(','):
810 if '+strictflags' not in encoding.environ.get('HGPLAIN', '').split(','):
811 exceptions.append('strictflags')
811 exceptions.append('strictflags')
812 if feature and exceptions:
812 if feature and exceptions:
813 return feature not in exceptions
813 return feature not in exceptions
814 return True
814 return True
815
815
816 def username(self, acceptempty=False):
816 def username(self, acceptempty=False):
817 """Return default username to be used in commits.
817 """Return default username to be used in commits.
818
818
819 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
819 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
820 and stop searching if one of these is set.
820 and stop searching if one of these is set.
821 If not found and acceptempty is True, returns None.
821 If not found and acceptempty is True, returns None.
822 If not found and ui.askusername is True, ask the user, else use
822 If not found and ui.askusername is True, ask the user, else use
823 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
823 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
824 If no username could be found, raise an Abort error.
824 If no username could be found, raise an Abort error.
825 """
825 """
826 user = encoding.environ.get("HGUSER")
826 user = encoding.environ.get("HGUSER")
827 if user is None:
827 if user is None:
828 user = self.config("ui", "username")
828 user = self.config("ui", "username")
829 if user is not None:
829 if user is not None:
830 user = os.path.expandvars(user)
830 user = os.path.expandvars(user)
831 if user is None:
831 if user is None:
832 user = encoding.environ.get("EMAIL")
832 user = encoding.environ.get("EMAIL")
833 if user is None and acceptempty:
833 if user is None and acceptempty:
834 return user
834 return user
835 if user is None and self.configbool("ui", "askusername"):
835 if user is None and self.configbool("ui", "askusername"):
836 user = self.prompt(_("enter a commit username:"), default=None)
836 user = self.prompt(_("enter a commit username:"), default=None)
837 if user is None and not self.interactive():
837 if user is None and not self.interactive():
838 try:
838 try:
839 user = '%s@%s' % (procutil.getuser(),
839 user = '%s@%s' % (procutil.getuser(),
840 encoding.strtolocal(socket.getfqdn()))
840 encoding.strtolocal(socket.getfqdn()))
841 self.warn(_("no username found, using '%s' instead\n") % user)
841 self.warn(_("no username found, using '%s' instead\n") % user)
842 except KeyError:
842 except KeyError:
843 pass
843 pass
844 if not user:
844 if not user:
845 raise error.Abort(_('no username supplied'),
845 raise error.Abort(_('no username supplied'),
846 hint=_("use 'hg config --edit' "
846 hint=_("use 'hg config --edit' "
847 'to set your username'))
847 'to set your username'))
848 if "\n" in user:
848 if "\n" in user:
849 raise error.Abort(_("username %r contains a newline\n")
849 raise error.Abort(_("username %r contains a newline\n")
850 % pycompat.bytestr(user))
850 % pycompat.bytestr(user))
851 return user
851 return user
852
852
853 def shortuser(self, user):
853 def shortuser(self, user):
854 """Return a short representation of a user name or email address."""
854 """Return a short representation of a user name or email address."""
855 if not self.verbose:
855 if not self.verbose:
856 user = stringutil.shortuser(user)
856 user = stringutil.shortuser(user)
857 return user
857 return user
858
858
859 def expandpath(self, loc, default=None):
859 def expandpath(self, loc, default=None):
860 """Return repository location relative to cwd or from [paths]"""
860 """Return repository location relative to cwd or from [paths]"""
861 try:
861 try:
862 p = self.paths.getpath(loc)
862 p = self.paths.getpath(loc)
863 if p:
863 if p:
864 return p.rawloc
864 return p.rawloc
865 except error.RepoError:
865 except error.RepoError:
866 pass
866 pass
867
867
868 if default:
868 if default:
869 try:
869 try:
870 p = self.paths.getpath(default)
870 p = self.paths.getpath(default)
871 if p:
871 if p:
872 return p.rawloc
872 return p.rawloc
873 except error.RepoError:
873 except error.RepoError:
874 pass
874 pass
875
875
876 return loc
876 return loc
877
877
878 @util.propertycache
878 @util.propertycache
879 def paths(self):
879 def paths(self):
880 return paths(self)
880 return paths(self)
881
881
882 def pushbuffer(self, error=False, subproc=False, labeled=False):
882 def pushbuffer(self, error=False, subproc=False, labeled=False):
883 """install a buffer to capture standard output of the ui object
883 """install a buffer to capture standard output of the ui object
884
884
885 If error is True, the error output will be captured too.
885 If error is True, the error output will be captured too.
886
886
887 If subproc is True, output from subprocesses (typically hooks) will be
887 If subproc is True, output from subprocesses (typically hooks) will be
888 captured too.
888 captured too.
889
889
890 If labeled is True, any labels associated with buffered
890 If labeled is True, any labels associated with buffered
891 output will be handled. By default, this has no effect
891 output will be handled. By default, this has no effect
892 on the output returned, but extensions and GUI tools may
892 on the output returned, but extensions and GUI tools may
893 handle this argument and returned styled output. If output
893 handle this argument and returned styled output. If output
894 is being buffered so it can be captured and parsed or
894 is being buffered so it can be captured and parsed or
895 processed, labeled should not be set to True.
895 processed, labeled should not be set to True.
896 """
896 """
897 self._buffers.append([])
897 self._buffers.append([])
898 self._bufferstates.append((error, subproc, labeled))
898 self._bufferstates.append((error, subproc, labeled))
899 self._bufferapplylabels = labeled
899 self._bufferapplylabels = labeled
900
900
901 def popbuffer(self):
901 def popbuffer(self):
902 '''pop the last buffer and return the buffered output'''
902 '''pop the last buffer and return the buffered output'''
903 self._bufferstates.pop()
903 self._bufferstates.pop()
904 if self._bufferstates:
904 if self._bufferstates:
905 self._bufferapplylabels = self._bufferstates[-1][2]
905 self._bufferapplylabels = self._bufferstates[-1][2]
906 else:
906 else:
907 self._bufferapplylabels = None
907 self._bufferapplylabels = None
908
908
909 return "".join(self._buffers.pop())
909 return "".join(self._buffers.pop())
910
910
911 def canwritewithoutlabels(self):
911 def canwritewithoutlabels(self):
912 '''check if write skips the label'''
912 '''check if write skips the label'''
913 if self._buffers and not self._bufferapplylabels:
913 if self._buffers and not self._bufferapplylabels:
914 return True
914 return True
915 return self._colormode is None
915 return self._colormode is None
916
916
917 def canbatchlabeledwrites(self):
917 def canbatchlabeledwrites(self):
918 '''check if write calls with labels are batchable'''
918 '''check if write calls with labels are batchable'''
919 # Windows color printing is special, see ``write``.
919 # Windows color printing is special, see ``write``.
920 return self._colormode != 'win32'
920 return self._colormode != 'win32'
921
921
922 def write(self, *args, **opts):
922 def write(self, *args, **opts):
923 '''write args to output
923 '''write args to output
924
924
925 By default, this method simply writes to the buffer or stdout.
925 By default, this method simply writes to the buffer or stdout.
926 Color mode can be set on the UI class to have the output decorated
926 Color mode can be set on the UI class to have the output decorated
927 with color modifier before being written to stdout.
927 with color modifier before being written to stdout.
928
928
929 The color used is controlled by an optional keyword argument, "label".
929 The color used is controlled by an optional keyword argument, "label".
930 This should be a string containing label names separated by space.
930 This should be a string containing label names separated by space.
931 Label names take the form of "topic.type". For example, ui.debug()
931 Label names take the form of "topic.type". For example, ui.debug()
932 issues a label of "ui.debug".
932 issues a label of "ui.debug".
933
933
934 When labeling output for a specific command, a label of
934 When labeling output for a specific command, a label of
935 "cmdname.type" is recommended. For example, status issues
935 "cmdname.type" is recommended. For example, status issues
936 a label of "status.modified" for modified files.
936 a label of "status.modified" for modified files.
937 '''
937 '''
938 if self._buffers:
938 if self._buffers:
939 if self._bufferapplylabels:
939 if self._bufferapplylabels:
940 label = opts.get(r'label', '')
940 label = opts.get(r'label', '')
941 self._buffers[-1].extend(self.label(a, label) for a in args)
941 self._buffers[-1].extend(self.label(a, label) for a in args)
942 else:
942 else:
943 self._buffers[-1].extend(args)
943 self._buffers[-1].extend(args)
944 else:
944 else:
945 self._writenobuf(*args, **opts)
945 self._writenobuf(*args, **opts)
946
946
947 def _writenobuf(self, *args, **opts):
947 def _writenobuf(self, *args, **opts):
948 if self._colormode == 'win32':
948 if self._colormode == 'win32':
949 # windows color printing is its own can of crab, defer to
949 # windows color printing is its own can of crab, defer to
950 # the color module and that is it.
950 # the color module and that is it.
951 color.win32print(self, self._write, *args, **opts)
951 color.win32print(self, self._write, *args, **opts)
952 else:
952 else:
953 msgs = args
953 msgs = args
954 if self._colormode is not None:
954 if self._colormode is not None:
955 label = opts.get(r'label', '')
955 label = opts.get(r'label', '')
956 msgs = [self.label(a, label) for a in args]
956 msgs = [self.label(a, label) for a in args]
957 self._write(*msgs, **opts)
957 self._write(*msgs, **opts)
958
958
959 def _write(self, *msgs, **opts):
959 def _write(self, *msgs, **opts):
960 self._progclear()
960 self._progclear()
961 # opencode timeblockedsection because this is a critical path
961 # opencode timeblockedsection because this is a critical path
962 starttime = util.timer()
962 starttime = util.timer()
963 try:
963 try:
964 self.fout.write(''.join(msgs))
964 self.fout.write(''.join(msgs))
965 except IOError as err:
965 except IOError as err:
966 raise error.StdioError(err)
966 raise error.StdioError(err)
967 finally:
967 finally:
968 self._blockedtimes['stdio_blocked'] += \
968 self._blockedtimes['stdio_blocked'] += \
969 (util.timer() - starttime) * 1000
969 (util.timer() - starttime) * 1000
970
970
971 def write_err(self, *args, **opts):
971 def write_err(self, *args, **opts):
972 self._progclear()
972 self._progclear()
973 if self._bufferstates and self._bufferstates[-1][0]:
973 if self._bufferstates and self._bufferstates[-1][0]:
974 self.write(*args, **opts)
974 self.write(*args, **opts)
975 elif self._colormode == 'win32':
975 elif self._colormode == 'win32':
976 # windows color printing is its own can of crab, defer to
976 # windows color printing is its own can of crab, defer to
977 # the color module and that is it.
977 # the color module and that is it.
978 color.win32print(self, self._write_err, *args, **opts)
978 color.win32print(self, self._write_err, *args, **opts)
979 else:
979 else:
980 msgs = args
980 msgs = args
981 if self._colormode is not None:
981 if self._colormode is not None:
982 label = opts.get(r'label', '')
982 label = opts.get(r'label', '')
983 msgs = [self.label(a, label) for a in args]
983 msgs = [self.label(a, label) for a in args]
984 self._write_err(*msgs, **opts)
984 self._write_err(*msgs, **opts)
985
985
986 def _write_err(self, *msgs, **opts):
986 def _write_err(self, *msgs, **opts):
987 try:
987 try:
988 with self.timeblockedsection('stdio'):
988 with self.timeblockedsection('stdio'):
989 if not getattr(self.fout, 'closed', False):
989 if not getattr(self.fout, 'closed', False):
990 self.fout.flush()
990 self.fout.flush()
991 for a in msgs:
991 for a in msgs:
992 self.ferr.write(a)
992 self.ferr.write(a)
993 # stderr may be buffered under win32 when redirected to files,
993 # stderr may be buffered under win32 when redirected to files,
994 # including stdout.
994 # including stdout.
995 if not getattr(self.ferr, 'closed', False):
995 if not getattr(self.ferr, 'closed', False):
996 self.ferr.flush()
996 self.ferr.flush()
997 except IOError as inst:
997 except IOError as inst:
998 if inst.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
998 if inst.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
999 raise error.StdioError(inst)
999 raise error.StdioError(inst)
1000
1000
1001 def flush(self):
1001 def flush(self):
1002 # opencode timeblockedsection because this is a critical path
1002 # opencode timeblockedsection because this is a critical path
1003 starttime = util.timer()
1003 starttime = util.timer()
1004 try:
1004 try:
1005 try:
1005 try:
1006 self.fout.flush()
1006 self.fout.flush()
1007 except IOError as err:
1007 except IOError as err:
1008 if err.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
1008 if err.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
1009 raise error.StdioError(err)
1009 raise error.StdioError(err)
1010 finally:
1010 finally:
1011 try:
1011 try:
1012 self.ferr.flush()
1012 self.ferr.flush()
1013 except IOError as err:
1013 except IOError as err:
1014 if err.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
1014 if err.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
1015 raise error.StdioError(err)
1015 raise error.StdioError(err)
1016 finally:
1016 finally:
1017 self._blockedtimes['stdio_blocked'] += \
1017 self._blockedtimes['stdio_blocked'] += \
1018 (util.timer() - starttime) * 1000
1018 (util.timer() - starttime) * 1000
1019
1019
1020 def _isatty(self, fh):
1020 def _isatty(self, fh):
1021 if self.configbool('ui', 'nontty'):
1021 if self.configbool('ui', 'nontty'):
1022 return False
1022 return False
1023 return procutil.isatty(fh)
1023 return procutil.isatty(fh)
1024
1024
1025 def disablepager(self):
1025 def disablepager(self):
1026 self._disablepager = True
1026 self._disablepager = True
1027
1027
1028 def pager(self, command):
1028 def pager(self, command):
1029 """Start a pager for subsequent command output.
1029 """Start a pager for subsequent command output.
1030
1030
1031 Commands which produce a long stream of output should call
1031 Commands which produce a long stream of output should call
1032 this function to activate the user's preferred pagination
1032 this function to activate the user's preferred pagination
1033 mechanism (which may be no pager). Calling this function
1033 mechanism (which may be no pager). Calling this function
1034 precludes any future use of interactive functionality, such as
1034 precludes any future use of interactive functionality, such as
1035 prompting the user or activating curses.
1035 prompting the user or activating curses.
1036
1036
1037 Args:
1037 Args:
1038 command: The full, non-aliased name of the command. That is, "log"
1038 command: The full, non-aliased name of the command. That is, "log"
1039 not "history, "summary" not "summ", etc.
1039 not "history, "summary" not "summ", etc.
1040 """
1040 """
1041 if (self._disablepager
1041 if (self._disablepager
1042 or self.pageractive):
1042 or self.pageractive):
1043 # how pager should do is already determined
1043 # how pager should do is already determined
1044 return
1044 return
1045
1045
1046 if not command.startswith('internal-always-') and (
1046 if not command.startswith('internal-always-') and (
1047 # explicit --pager=on (= 'internal-always-' prefix) should
1047 # explicit --pager=on (= 'internal-always-' prefix) should
1048 # take precedence over disabling factors below
1048 # take precedence over disabling factors below
1049 command in self.configlist('pager', 'ignore')
1049 command in self.configlist('pager', 'ignore')
1050 or not self.configbool('ui', 'paginate')
1050 or not self.configbool('ui', 'paginate')
1051 or not self.configbool('pager', 'attend-' + command, True)
1051 or not self.configbool('pager', 'attend-' + command, True)
1052 # TODO: if we want to allow HGPLAINEXCEPT=pager,
1052 # TODO: if we want to allow HGPLAINEXCEPT=pager,
1053 # formatted() will need some adjustment.
1053 # formatted() will need some adjustment.
1054 or not self.formatted()
1054 or not self.formatted()
1055 or self.plain()
1055 or self.plain()
1056 or self._buffers
1056 or self._buffers
1057 # TODO: expose debugger-enabled on the UI object
1057 # TODO: expose debugger-enabled on the UI object
1058 or '--debugger' in pycompat.sysargv):
1058 or '--debugger' in pycompat.sysargv):
1059 # We only want to paginate if the ui appears to be
1059 # We only want to paginate if the ui appears to be
1060 # interactive, the user didn't say HGPLAIN or
1060 # interactive, the user didn't say HGPLAIN or
1061 # HGPLAINEXCEPT=pager, and the user didn't specify --debug.
1061 # HGPLAINEXCEPT=pager, and the user didn't specify --debug.
1062 return
1062 return
1063
1063
1064 pagercmd = self.config('pager', 'pager', rcutil.fallbackpager)
1064 pagercmd = self.config('pager', 'pager', rcutil.fallbackpager)
1065 if not pagercmd:
1065 if not pagercmd:
1066 return
1066 return
1067
1067
1068 pagerenv = {}
1068 pagerenv = {}
1069 for name, value in rcutil.defaultpagerenv().items():
1069 for name, value in rcutil.defaultpagerenv().items():
1070 if name not in encoding.environ:
1070 if name not in encoding.environ:
1071 pagerenv[name] = value
1071 pagerenv[name] = value
1072
1072
1073 self.debug('starting pager for command %r\n' % command)
1073 self.debug('starting pager for command %r\n' % command)
1074 self.flush()
1074 self.flush()
1075
1075
1076 wasformatted = self.formatted()
1076 wasformatted = self.formatted()
1077 if util.safehasattr(signal, "SIGPIPE"):
1077 if util.safehasattr(signal, "SIGPIPE"):
1078 signal.signal(signal.SIGPIPE, _catchterm)
1078 signal.signal(signal.SIGPIPE, _catchterm)
1079 if self._runpager(pagercmd, pagerenv):
1079 if self._runpager(pagercmd, pagerenv):
1080 self.pageractive = True
1080 self.pageractive = True
1081 # Preserve the formatted-ness of the UI. This is important
1081 # Preserve the formatted-ness of the UI. This is important
1082 # because we mess with stdout, which might confuse
1082 # because we mess with stdout, which might confuse
1083 # auto-detection of things being formatted.
1083 # auto-detection of things being formatted.
1084 self.setconfig('ui', 'formatted', wasformatted, 'pager')
1084 self.setconfig('ui', 'formatted', wasformatted, 'pager')
1085 self.setconfig('ui', 'interactive', False, 'pager')
1085 self.setconfig('ui', 'interactive', False, 'pager')
1086
1086
1087 # If pagermode differs from color.mode, reconfigure color now that
1087 # If pagermode differs from color.mode, reconfigure color now that
1088 # pageractive is set.
1088 # pageractive is set.
1089 cm = self._colormode
1089 cm = self._colormode
1090 if cm != self.config('color', 'pagermode', cm):
1090 if cm != self.config('color', 'pagermode', cm):
1091 color.setup(self)
1091 color.setup(self)
1092 else:
1092 else:
1093 # If the pager can't be spawned in dispatch when --pager=on is
1093 # If the pager can't be spawned in dispatch when --pager=on is
1094 # given, don't try again when the command runs, to avoid a duplicate
1094 # given, don't try again when the command runs, to avoid a duplicate
1095 # warning about a missing pager command.
1095 # warning about a missing pager command.
1096 self.disablepager()
1096 self.disablepager()
1097
1097
1098 def _runpager(self, command, env=None):
1098 def _runpager(self, command, env=None):
1099 """Actually start the pager and set up file descriptors.
1099 """Actually start the pager and set up file descriptors.
1100
1100
1101 This is separate in part so that extensions (like chg) can
1101 This is separate in part so that extensions (like chg) can
1102 override how a pager is invoked.
1102 override how a pager is invoked.
1103 """
1103 """
1104 if command == 'cat':
1104 if command == 'cat':
1105 # Save ourselves some work.
1105 # Save ourselves some work.
1106 return False
1106 return False
1107 # If the command doesn't contain any of these characters, we
1107 # If the command doesn't contain any of these characters, we
1108 # assume it's a binary and exec it directly. This means for
1108 # assume it's a binary and exec it directly. This means for
1109 # simple pager command configurations, we can degrade
1109 # simple pager command configurations, we can degrade
1110 # gracefully and tell the user about their broken pager.
1110 # gracefully and tell the user about their broken pager.
1111 shell = any(c in command for c in "|&;<>()$`\\\"' \t\n*?[#~=%")
1111 shell = any(c in command for c in "|&;<>()$`\\\"' \t\n*?[#~=%")
1112
1112
1113 if pycompat.iswindows and not shell:
1113 if pycompat.iswindows and not shell:
1114 # Window's built-in `more` cannot be invoked with shell=False, but
1114 # Window's built-in `more` cannot be invoked with shell=False, but
1115 # its `more.com` can. Hide this implementation detail from the
1115 # its `more.com` can. Hide this implementation detail from the
1116 # user so we can also get sane bad PAGER behavior. MSYS has
1116 # user so we can also get sane bad PAGER behavior. MSYS has
1117 # `more.exe`, so do a cmd.exe style resolution of the executable to
1117 # `more.exe`, so do a cmd.exe style resolution of the executable to
1118 # determine which one to use.
1118 # determine which one to use.
1119 fullcmd = procutil.findexe(command)
1119 fullcmd = procutil.findexe(command)
1120 if not fullcmd:
1120 if not fullcmd:
1121 self.warn(_("missing pager command '%s', skipping pager\n")
1121 self.warn(_("missing pager command '%s', skipping pager\n")
1122 % command)
1122 % command)
1123 return False
1123 return False
1124
1124
1125 command = fullcmd
1125 command = fullcmd
1126
1126
1127 try:
1127 try:
1128 pager = subprocess.Popen(
1128 pager = subprocess.Popen(
1129 command, shell=shell, bufsize=-1,
1129 command, shell=shell, bufsize=-1,
1130 close_fds=procutil.closefds, stdin=subprocess.PIPE,
1130 close_fds=procutil.closefds, stdin=subprocess.PIPE,
1131 stdout=procutil.stdout, stderr=procutil.stderr,
1131 stdout=procutil.stdout, stderr=procutil.stderr,
1132 env=procutil.shellenviron(env))
1132 env=procutil.shellenviron(env))
1133 except OSError as e:
1133 except OSError as e:
1134 if e.errno == errno.ENOENT and not shell:
1134 if e.errno == errno.ENOENT and not shell:
1135 self.warn(_("missing pager command '%s', skipping pager\n")
1135 self.warn(_("missing pager command '%s', skipping pager\n")
1136 % command)
1136 % command)
1137 return False
1137 return False
1138 raise
1138 raise
1139
1139
1140 # back up original file descriptors
1140 # back up original file descriptors
1141 stdoutfd = os.dup(procutil.stdout.fileno())
1141 stdoutfd = os.dup(procutil.stdout.fileno())
1142 stderrfd = os.dup(procutil.stderr.fileno())
1142 stderrfd = os.dup(procutil.stderr.fileno())
1143
1143
1144 os.dup2(pager.stdin.fileno(), procutil.stdout.fileno())
1144 os.dup2(pager.stdin.fileno(), procutil.stdout.fileno())
1145 if self._isatty(procutil.stderr):
1145 if self._isatty(procutil.stderr):
1146 os.dup2(pager.stdin.fileno(), procutil.stderr.fileno())
1146 os.dup2(pager.stdin.fileno(), procutil.stderr.fileno())
1147
1147
1148 @self.atexit
1148 @self.atexit
1149 def killpager():
1149 def killpager():
1150 if util.safehasattr(signal, "SIGINT"):
1150 if util.safehasattr(signal, "SIGINT"):
1151 signal.signal(signal.SIGINT, signal.SIG_IGN)
1151 signal.signal(signal.SIGINT, signal.SIG_IGN)
1152 # restore original fds, closing pager.stdin copies in the process
1152 # restore original fds, closing pager.stdin copies in the process
1153 os.dup2(stdoutfd, procutil.stdout.fileno())
1153 os.dup2(stdoutfd, procutil.stdout.fileno())
1154 os.dup2(stderrfd, procutil.stderr.fileno())
1154 os.dup2(stderrfd, procutil.stderr.fileno())
1155 pager.stdin.close()
1155 pager.stdin.close()
1156 pager.wait()
1156 pager.wait()
1157
1157
1158 return True
1158 return True
1159
1159
1160 @property
1160 @property
1161 def _exithandlers(self):
1161 def _exithandlers(self):
1162 return _reqexithandlers
1162 return _reqexithandlers
1163
1163
1164 def atexit(self, func, *args, **kwargs):
1164 def atexit(self, func, *args, **kwargs):
1165 '''register a function to run after dispatching a request
1165 '''register a function to run after dispatching a request
1166
1166
1167 Handlers do not stay registered across request boundaries.'''
1167 Handlers do not stay registered across request boundaries.'''
1168 self._exithandlers.append((func, args, kwargs))
1168 self._exithandlers.append((func, args, kwargs))
1169 return func
1169 return func
1170
1170
1171 def interface(self, feature):
1171 def interface(self, feature):
1172 """what interface to use for interactive console features?
1172 """what interface to use for interactive console features?
1173
1173
1174 The interface is controlled by the value of `ui.interface` but also by
1174 The interface is controlled by the value of `ui.interface` but also by
1175 the value of feature-specific configuration. For example:
1175 the value of feature-specific configuration. For example:
1176
1176
1177 ui.interface.histedit = text
1177 ui.interface.histedit = text
1178 ui.interface.chunkselector = curses
1178 ui.interface.chunkselector = curses
1179
1179
1180 Here the features are "histedit" and "chunkselector".
1180 Here the features are "histedit" and "chunkselector".
1181
1181
1182 The configuration above means that the default interfaces for commands
1182 The configuration above means that the default interfaces for commands
1183 is curses, the interface for histedit is text and the interface for
1183 is curses, the interface for histedit is text and the interface for
1184 selecting chunk is crecord (the best curses interface available).
1184 selecting chunk is crecord (the best curses interface available).
1185
1185
1186 Consider the following example:
1186 Consider the following example:
1187 ui.interface = curses
1187 ui.interface = curses
1188 ui.interface.histedit = text
1188 ui.interface.histedit = text
1189
1189
1190 Then histedit will use the text interface and chunkselector will use
1190 Then histedit will use the text interface and chunkselector will use
1191 the default curses interface (crecord at the moment).
1191 the default curses interface (crecord at the moment).
1192 """
1192 """
1193 alldefaults = frozenset(["text", "curses"])
1193 alldefaults = frozenset(["text", "curses"])
1194
1194
1195 featureinterfaces = {
1195 featureinterfaces = {
1196 "chunkselector": [
1196 "chunkselector": [
1197 "text",
1197 "text",
1198 "curses",
1198 "curses",
1199 ]
1199 ]
1200 }
1200 }
1201
1201
1202 # Feature-specific interface
1202 # Feature-specific interface
1203 if feature not in featureinterfaces.keys():
1203 if feature not in featureinterfaces.keys():
1204 # Programming error, not user error
1204 # Programming error, not user error
1205 raise ValueError("Unknown feature requested %s" % feature)
1205 raise ValueError("Unknown feature requested %s" % feature)
1206
1206
1207 availableinterfaces = frozenset(featureinterfaces[feature])
1207 availableinterfaces = frozenset(featureinterfaces[feature])
1208 if alldefaults > availableinterfaces:
1208 if alldefaults > availableinterfaces:
1209 # Programming error, not user error. We need a use case to
1209 # Programming error, not user error. We need a use case to
1210 # define the right thing to do here.
1210 # define the right thing to do here.
1211 raise ValueError(
1211 raise ValueError(
1212 "Feature %s does not handle all default interfaces" %
1212 "Feature %s does not handle all default interfaces" %
1213 feature)
1213 feature)
1214
1214
1215 if self.plain() or encoding.environ.get('TERM') == 'dumb':
1215 if self.plain() or encoding.environ.get('TERM') == 'dumb':
1216 return "text"
1216 return "text"
1217
1217
1218 # Default interface for all the features
1218 # Default interface for all the features
1219 defaultinterface = "text"
1219 defaultinterface = "text"
1220 i = self.config("ui", "interface")
1220 i = self.config("ui", "interface")
1221 if i in alldefaults:
1221 if i in alldefaults:
1222 defaultinterface = i
1222 defaultinterface = i
1223
1223
1224 choseninterface = defaultinterface
1224 choseninterface = defaultinterface
1225 f = self.config("ui", "interface.%s" % feature)
1225 f = self.config("ui", "interface.%s" % feature)
1226 if f in availableinterfaces:
1226 if f in availableinterfaces:
1227 choseninterface = f
1227 choseninterface = f
1228
1228
1229 if i is not None and defaultinterface != i:
1229 if i is not None and defaultinterface != i:
1230 if f is not None:
1230 if f is not None:
1231 self.warn(_("invalid value for ui.interface: %s\n") %
1231 self.warn(_("invalid value for ui.interface: %s\n") %
1232 (i,))
1232 (i,))
1233 else:
1233 else:
1234 self.warn(_("invalid value for ui.interface: %s (using %s)\n") %
1234 self.warn(_("invalid value for ui.interface: %s (using %s)\n") %
1235 (i, choseninterface))
1235 (i, choseninterface))
1236 if f is not None and choseninterface != f:
1236 if f is not None and choseninterface != f:
1237 self.warn(_("invalid value for ui.interface.%s: %s (using %s)\n") %
1237 self.warn(_("invalid value for ui.interface.%s: %s (using %s)\n") %
1238 (feature, f, choseninterface))
1238 (feature, f, choseninterface))
1239
1239
1240 return choseninterface
1240 return choseninterface
1241
1241
1242 def interactive(self):
1242 def interactive(self):
1243 '''is interactive input allowed?
1243 '''is interactive input allowed?
1244
1244
1245 An interactive session is a session where input can be reasonably read
1245 An interactive session is a session where input can be reasonably read
1246 from `sys.stdin'. If this function returns false, any attempt to read
1246 from `sys.stdin'. If this function returns false, any attempt to read
1247 from stdin should fail with an error, unless a sensible default has been
1247 from stdin should fail with an error, unless a sensible default has been
1248 specified.
1248 specified.
1249
1249
1250 Interactiveness is triggered by the value of the `ui.interactive'
1250 Interactiveness is triggered by the value of the `ui.interactive'
1251 configuration variable or - if it is unset - when `sys.stdin' points
1251 configuration variable or - if it is unset - when `sys.stdin' points
1252 to a terminal device.
1252 to a terminal device.
1253
1253
1254 This function refers to input only; for output, see `ui.formatted()'.
1254 This function refers to input only; for output, see `ui.formatted()'.
1255 '''
1255 '''
1256 i = self.configbool("ui", "interactive")
1256 i = self.configbool("ui", "interactive")
1257 if i is None:
1257 if i is None:
1258 # some environments replace stdin without implementing isatty
1258 # some environments replace stdin without implementing isatty
1259 # usually those are non-interactive
1259 # usually those are non-interactive
1260 return self._isatty(self.fin)
1260 return self._isatty(self.fin)
1261
1261
1262 return i
1262 return i
1263
1263
1264 def termwidth(self):
1264 def termwidth(self):
1265 '''how wide is the terminal in columns?
1265 '''how wide is the terminal in columns?
1266 '''
1266 '''
1267 if 'COLUMNS' in encoding.environ:
1267 if 'COLUMNS' in encoding.environ:
1268 try:
1268 try:
1269 return int(encoding.environ['COLUMNS'])
1269 return int(encoding.environ['COLUMNS'])
1270 except ValueError:
1270 except ValueError:
1271 pass
1271 pass
1272 return scmutil.termsize(self)[0]
1272 return scmutil.termsize(self)[0]
1273
1273
1274 def formatted(self):
1274 def formatted(self):
1275 '''should formatted output be used?
1275 '''should formatted output be used?
1276
1276
1277 It is often desirable to format the output to suite the output medium.
1277 It is often desirable to format the output to suite the output medium.
1278 Examples of this are truncating long lines or colorizing messages.
1278 Examples of this are truncating long lines or colorizing messages.
1279 However, this is not often not desirable when piping output into other
1279 However, this is not often not desirable when piping output into other
1280 utilities, e.g. `grep'.
1280 utilities, e.g. `grep'.
1281
1281
1282 Formatted output is triggered by the value of the `ui.formatted'
1282 Formatted output is triggered by the value of the `ui.formatted'
1283 configuration variable or - if it is unset - when `sys.stdout' points
1283 configuration variable or - if it is unset - when `sys.stdout' points
1284 to a terminal device. Please note that `ui.formatted' should be
1284 to a terminal device. Please note that `ui.formatted' should be
1285 considered an implementation detail; it is not intended for use outside
1285 considered an implementation detail; it is not intended for use outside
1286 Mercurial or its extensions.
1286 Mercurial or its extensions.
1287
1287
1288 This function refers to output only; for input, see `ui.interactive()'.
1288 This function refers to output only; for input, see `ui.interactive()'.
1289 This function always returns false when in plain mode, see `ui.plain()'.
1289 This function always returns false when in plain mode, see `ui.plain()'.
1290 '''
1290 '''
1291 if self.plain():
1291 if self.plain():
1292 return False
1292 return False
1293
1293
1294 i = self.configbool("ui", "formatted")
1294 i = self.configbool("ui", "formatted")
1295 if i is None:
1295 if i is None:
1296 # some environments replace stdout without implementing isatty
1296 # some environments replace stdout without implementing isatty
1297 # usually those are non-interactive
1297 # usually those are non-interactive
1298 return self._isatty(self.fout)
1298 return self._isatty(self.fout)
1299
1299
1300 return i
1300 return i
1301
1301
1302 def _readline(self):
1302 def _readline(self):
1303 # Replacing stdin/stdout temporarily is a hard problem on Python 3
1303 # Replacing stdin/stdout temporarily is a hard problem on Python 3
1304 # because they have to be text streams with *no buffering*. Instead,
1304 # because they have to be text streams with *no buffering*. Instead,
1305 # we use rawinput() only if call_readline() will be invoked by
1305 # we use rawinput() only if call_readline() will be invoked by
1306 # PyOS_Readline(), so no I/O will be made at Python layer.
1306 # PyOS_Readline(), so no I/O will be made at Python layer.
1307 usereadline = (self._isatty(self.fin) and self._isatty(self.fout)
1307 usereadline = (self._isatty(self.fin) and self._isatty(self.fout)
1308 and procutil.isstdin(self.fin)
1308 and procutil.isstdin(self.fin)
1309 and procutil.isstdout(self.fout))
1309 and procutil.isstdout(self.fout))
1310 if usereadline:
1310 if usereadline:
1311 try:
1311 try:
1312 # magically add command line editing support, where
1312 # magically add command line editing support, where
1313 # available
1313 # available
1314 import readline
1314 import readline
1315 # force demandimport to really load the module
1315 # force demandimport to really load the module
1316 readline.read_history_file
1316 readline.read_history_file
1317 # windows sometimes raises something other than ImportError
1317 # windows sometimes raises something other than ImportError
1318 except Exception:
1318 except Exception:
1319 usereadline = False
1319 usereadline = False
1320
1320
1321 # prompt ' ' must exist; otherwise readline may delete entire line
1321 # prompt ' ' must exist; otherwise readline may delete entire line
1322 # - http://bugs.python.org/issue12833
1322 # - http://bugs.python.org/issue12833
1323 with self.timeblockedsection('stdio'):
1323 with self.timeblockedsection('stdio'):
1324 if usereadline:
1324 if usereadline:
1325 line = encoding.strtolocal(pycompat.rawinput(r' '))
1325 line = encoding.strtolocal(pycompat.rawinput(r' '))
1326 # When stdin is in binary mode on Windows, it can cause
1326 # When stdin is in binary mode on Windows, it can cause
1327 # raw_input() to emit an extra trailing carriage return
1327 # raw_input() to emit an extra trailing carriage return
1328 if pycompat.oslinesep == b'\r\n' and line.endswith(b'\r'):
1328 if pycompat.oslinesep == b'\r\n' and line.endswith(b'\r'):
1329 line = line[:-1]
1329 line = line[:-1]
1330 else:
1330 else:
1331 self.fout.write(b' ')
1331 self.fout.write(b' ')
1332 self.fout.flush()
1332 self.fout.flush()
1333 line = self.fin.readline()
1333 line = self.fin.readline()
1334 if not line:
1334 if not line:
1335 raise EOFError
1335 raise EOFError
1336 line = line.rstrip(pycompat.oslinesep)
1336 line = line.rstrip(pycompat.oslinesep)
1337
1337
1338 return line
1338 return line
1339
1339
1340 def prompt(self, msg, default="y"):
1340 def prompt(self, msg, default="y"):
1341 """Prompt user with msg, read response.
1341 """Prompt user with msg, read response.
1342 If ui is not interactive, the default is returned.
1342 If ui is not interactive, the default is returned.
1343 """
1343 """
1344 if not self.interactive():
1344 if not self.interactive():
1345 self.write(msg, ' ', default or '', "\n")
1345 self.write(msg, ' ', default or '', "\n")
1346 return default
1346 return default
1347 self._writenobuf(msg, label='ui.prompt')
1347 self._writenobuf(msg, label='ui.prompt')
1348 self.flush()
1348 self.flush()
1349 try:
1349 try:
1350 r = self._readline()
1350 r = self._readline()
1351 if not r:
1351 if not r:
1352 r = default
1352 r = default
1353 if self.configbool('ui', 'promptecho'):
1353 if self.configbool('ui', 'promptecho'):
1354 self.write(r, "\n")
1354 self.write(r, "\n")
1355 return r
1355 return r
1356 except EOFError:
1356 except EOFError:
1357 raise error.ResponseExpected()
1357 raise error.ResponseExpected()
1358
1358
1359 @staticmethod
1359 @staticmethod
1360 def extractchoices(prompt):
1360 def extractchoices(prompt):
1361 """Extract prompt message and list of choices from specified prompt.
1361 """Extract prompt message and list of choices from specified prompt.
1362
1362
1363 This returns tuple "(message, choices)", and "choices" is the
1363 This returns tuple "(message, choices)", and "choices" is the
1364 list of tuple "(response character, text without &)".
1364 list of tuple "(response character, text without &)".
1365
1365
1366 >>> ui.extractchoices(b"awake? $$ &Yes $$ &No")
1366 >>> ui.extractchoices(b"awake? $$ &Yes $$ &No")
1367 ('awake? ', [('y', 'Yes'), ('n', 'No')])
1367 ('awake? ', [('y', 'Yes'), ('n', 'No')])
1368 >>> ui.extractchoices(b"line\\nbreak? $$ &Yes $$ &No")
1368 >>> ui.extractchoices(b"line\\nbreak? $$ &Yes $$ &No")
1369 ('line\\nbreak? ', [('y', 'Yes'), ('n', 'No')])
1369 ('line\\nbreak? ', [('y', 'Yes'), ('n', 'No')])
1370 >>> ui.extractchoices(b"want lots of $$money$$?$$Ye&s$$N&o")
1370 >>> ui.extractchoices(b"want lots of $$money$$?$$Ye&s$$N&o")
1371 ('want lots of $$money$$?', [('s', 'Yes'), ('o', 'No')])
1371 ('want lots of $$money$$?', [('s', 'Yes'), ('o', 'No')])
1372 """
1372 """
1373
1373
1374 # Sadly, the prompt string may have been built with a filename
1374 # Sadly, the prompt string may have been built with a filename
1375 # containing "$$" so let's try to find the first valid-looking
1375 # containing "$$" so let's try to find the first valid-looking
1376 # prompt to start parsing. Sadly, we also can't rely on
1376 # prompt to start parsing. Sadly, we also can't rely on
1377 # choices containing spaces, ASCII, or basically anything
1377 # choices containing spaces, ASCII, or basically anything
1378 # except an ampersand followed by a character.
1378 # except an ampersand followed by a character.
1379 m = re.match(br'(?s)(.+?)\$\$([^\$]*&[^ \$].*)', prompt)
1379 m = re.match(br'(?s)(.+?)\$\$([^\$]*&[^ \$].*)', prompt)
1380 msg = m.group(1)
1380 msg = m.group(1)
1381 choices = [p.strip(' ') for p in m.group(2).split('$$')]
1381 choices = [p.strip(' ') for p in m.group(2).split('$$')]
1382 def choicetuple(s):
1382 def choicetuple(s):
1383 ampidx = s.index('&')
1383 ampidx = s.index('&')
1384 return s[ampidx + 1:ampidx + 2].lower(), s.replace('&', '', 1)
1384 return s[ampidx + 1:ampidx + 2].lower(), s.replace('&', '', 1)
1385 return (msg, [choicetuple(s) for s in choices])
1385 return (msg, [choicetuple(s) for s in choices])
1386
1386
1387 def promptchoice(self, prompt, default=0):
1387 def promptchoice(self, prompt, default=0):
1388 """Prompt user with a message, read response, and ensure it matches
1388 """Prompt user with a message, read response, and ensure it matches
1389 one of the provided choices. The prompt is formatted as follows:
1389 one of the provided choices. The prompt is formatted as follows:
1390
1390
1391 "would you like fries with that (Yn)? $$ &Yes $$ &No"
1391 "would you like fries with that (Yn)? $$ &Yes $$ &No"
1392
1392
1393 The index of the choice is returned. Responses are case
1393 The index of the choice is returned. Responses are case
1394 insensitive. If ui is not interactive, the default is
1394 insensitive. If ui is not interactive, the default is
1395 returned.
1395 returned.
1396 """
1396 """
1397
1397
1398 msg, choices = self.extractchoices(prompt)
1398 msg, choices = self.extractchoices(prompt)
1399 resps = [r for r, t in choices]
1399 resps = [r for r, t in choices]
1400 while True:
1400 while True:
1401 r = self.prompt(msg, resps[default])
1401 r = self.prompt(msg, resps[default])
1402 if r.lower() in resps:
1402 if r.lower() in resps:
1403 return resps.index(r.lower())
1403 return resps.index(r.lower())
1404 self.write(_("unrecognized response\n"))
1404 self.write(_("unrecognized response\n"))
1405
1405
1406 def getpass(self, prompt=None, default=None):
1406 def getpass(self, prompt=None, default=None):
1407 if not self.interactive():
1407 if not self.interactive():
1408 return default
1408 return default
1409 try:
1409 try:
1410 self.write_err(self.label(prompt or _('password: '), 'ui.prompt'))
1410 self.write_err(self.label(prompt or _('password: '), 'ui.prompt'))
1411 # disable getpass() only if explicitly specified. it's still valid
1411 # disable getpass() only if explicitly specified. it's still valid
1412 # to interact with tty even if fin is not a tty.
1412 # to interact with tty even if fin is not a tty.
1413 with self.timeblockedsection('stdio'):
1413 with self.timeblockedsection('stdio'):
1414 if self.configbool('ui', 'nontty'):
1414 if self.configbool('ui', 'nontty'):
1415 l = self.fin.readline()
1415 l = self.fin.readline()
1416 if not l:
1416 if not l:
1417 raise EOFError
1417 raise EOFError
1418 return l.rstrip('\n')
1418 return l.rstrip('\n')
1419 else:
1419 else:
1420 return getpass.getpass('')
1420 return getpass.getpass('')
1421 except EOFError:
1421 except EOFError:
1422 raise error.ResponseExpected()
1422 raise error.ResponseExpected()
1423
1423 def status(self, *msg, **opts):
1424 def status(self, *msg, **opts):
1424 '''write status message to output (if ui.quiet is False)
1425 '''write status message to output (if ui.quiet is False)
1425
1426
1426 This adds an output label of "ui.status".
1427 This adds an output label of "ui.status".
1427 '''
1428 '''
1428 if not self.quiet:
1429 if not self.quiet:
1429 opts[r'label'] = opts.get(r'label', '') + ' ui.status'
1430 opts[r'label'] = opts.get(r'label', '') + ' ui.status'
1430 self.write(*msg, **opts)
1431 self.write(*msg, **opts)
1432
1431 def warn(self, *msg, **opts):
1433 def warn(self, *msg, **opts):
1432 '''write warning message to output (stderr)
1434 '''write warning message to output (stderr)
1433
1435
1434 This adds an output label of "ui.warning".
1436 This adds an output label of "ui.warning".
1435 '''
1437 '''
1436 opts[r'label'] = opts.get(r'label', '') + ' ui.warning'
1438 opts[r'label'] = opts.get(r'label', '') + ' ui.warning'
1437 self.write_err(*msg, **opts)
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 def note(self, *msg, **opts):
1449 def note(self, *msg, **opts):
1439 '''write note to output (if ui.verbose is True)
1450 '''write note to output (if ui.verbose is True)
1440
1451
1441 This adds an output label of "ui.note".
1452 This adds an output label of "ui.note".
1442 '''
1453 '''
1443 if self.verbose:
1454 if self.verbose:
1444 opts[r'label'] = opts.get(r'label', '') + ' ui.note'
1455 opts[r'label'] = opts.get(r'label', '') + ' ui.note'
1445 self.write(*msg, **opts)
1456 self.write(*msg, **opts)
1457
1446 def debug(self, *msg, **opts):
1458 def debug(self, *msg, **opts):
1447 '''write debug message to output (if ui.debugflag is True)
1459 '''write debug message to output (if ui.debugflag is True)
1448
1460
1449 This adds an output label of "ui.debug".
1461 This adds an output label of "ui.debug".
1450 '''
1462 '''
1451 if self.debugflag:
1463 if self.debugflag:
1452 opts[r'label'] = opts.get(r'label', '') + ' ui.debug'
1464 opts[r'label'] = opts.get(r'label', '') + ' ui.debug'
1453 self.write(*msg, **opts)
1465 self.write(*msg, **opts)
1454
1466
1455 def edit(self, text, user, extra=None, editform=None, pending=None,
1467 def edit(self, text, user, extra=None, editform=None, pending=None,
1456 repopath=None, action=None):
1468 repopath=None, action=None):
1457 if action is None:
1469 if action is None:
1458 self.develwarn('action is None but will soon be a required '
1470 self.develwarn('action is None but will soon be a required '
1459 'parameter to ui.edit()')
1471 'parameter to ui.edit()')
1460 extra_defaults = {
1472 extra_defaults = {
1461 'prefix': 'editor',
1473 'prefix': 'editor',
1462 'suffix': '.txt',
1474 'suffix': '.txt',
1463 }
1475 }
1464 if extra is not None:
1476 if extra is not None:
1465 if extra.get('suffix') is not None:
1477 if extra.get('suffix') is not None:
1466 self.develwarn('extra.suffix is not None but will soon be '
1478 self.develwarn('extra.suffix is not None but will soon be '
1467 'ignored by ui.edit()')
1479 'ignored by ui.edit()')
1468 extra_defaults.update(extra)
1480 extra_defaults.update(extra)
1469 extra = extra_defaults
1481 extra = extra_defaults
1470
1482
1471 if action == 'diff':
1483 if action == 'diff':
1472 suffix = '.diff'
1484 suffix = '.diff'
1473 elif action:
1485 elif action:
1474 suffix = '.%s.hg.txt' % action
1486 suffix = '.%s.hg.txt' % action
1475 else:
1487 else:
1476 suffix = extra['suffix']
1488 suffix = extra['suffix']
1477
1489
1478 rdir = None
1490 rdir = None
1479 if self.configbool('experimental', 'editortmpinhg'):
1491 if self.configbool('experimental', 'editortmpinhg'):
1480 rdir = repopath
1492 rdir = repopath
1481 (fd, name) = pycompat.mkstemp(prefix='hg-' + extra['prefix'] + '-',
1493 (fd, name) = pycompat.mkstemp(prefix='hg-' + extra['prefix'] + '-',
1482 suffix=suffix,
1494 suffix=suffix,
1483 dir=rdir)
1495 dir=rdir)
1484 try:
1496 try:
1485 f = os.fdopen(fd, r'wb')
1497 f = os.fdopen(fd, r'wb')
1486 f.write(util.tonativeeol(text))
1498 f.write(util.tonativeeol(text))
1487 f.close()
1499 f.close()
1488
1500
1489 environ = {'HGUSER': user}
1501 environ = {'HGUSER': user}
1490 if 'transplant_source' in extra:
1502 if 'transplant_source' in extra:
1491 environ.update({'HGREVISION': hex(extra['transplant_source'])})
1503 environ.update({'HGREVISION': hex(extra['transplant_source'])})
1492 for label in ('intermediate-source', 'source', 'rebase_source'):
1504 for label in ('intermediate-source', 'source', 'rebase_source'):
1493 if label in extra:
1505 if label in extra:
1494 environ.update({'HGREVISION': extra[label]})
1506 environ.update({'HGREVISION': extra[label]})
1495 break
1507 break
1496 if editform:
1508 if editform:
1497 environ.update({'HGEDITFORM': editform})
1509 environ.update({'HGEDITFORM': editform})
1498 if pending:
1510 if pending:
1499 environ.update({'HG_PENDING': pending})
1511 environ.update({'HG_PENDING': pending})
1500
1512
1501 editor = self.geteditor()
1513 editor = self.geteditor()
1502
1514
1503 self.system("%s \"%s\"" % (editor, name),
1515 self.system("%s \"%s\"" % (editor, name),
1504 environ=environ,
1516 environ=environ,
1505 onerr=error.Abort, errprefix=_("edit failed"),
1517 onerr=error.Abort, errprefix=_("edit failed"),
1506 blockedtag='editor')
1518 blockedtag='editor')
1507
1519
1508 f = open(name, r'rb')
1520 f = open(name, r'rb')
1509 t = util.fromnativeeol(f.read())
1521 t = util.fromnativeeol(f.read())
1510 f.close()
1522 f.close()
1511 finally:
1523 finally:
1512 os.unlink(name)
1524 os.unlink(name)
1513
1525
1514 return t
1526 return t
1515
1527
1516 def system(self, cmd, environ=None, cwd=None, onerr=None, errprefix=None,
1528 def system(self, cmd, environ=None, cwd=None, onerr=None, errprefix=None,
1517 blockedtag=None):
1529 blockedtag=None):
1518 '''execute shell command with appropriate output stream. command
1530 '''execute shell command with appropriate output stream. command
1519 output will be redirected if fout is not stdout.
1531 output will be redirected if fout is not stdout.
1520
1532
1521 if command fails and onerr is None, return status, else raise onerr
1533 if command fails and onerr is None, return status, else raise onerr
1522 object as exception.
1534 object as exception.
1523 '''
1535 '''
1524 if blockedtag is None:
1536 if blockedtag is None:
1525 # Long cmds tend to be because of an absolute path on cmd. Keep
1537 # Long cmds tend to be because of an absolute path on cmd. Keep
1526 # the tail end instead
1538 # the tail end instead
1527 cmdsuffix = cmd.translate(None, _keepalnum)[-85:]
1539 cmdsuffix = cmd.translate(None, _keepalnum)[-85:]
1528 blockedtag = 'unknown_system_' + cmdsuffix
1540 blockedtag = 'unknown_system_' + cmdsuffix
1529 out = self.fout
1541 out = self.fout
1530 if any(s[1] for s in self._bufferstates):
1542 if any(s[1] for s in self._bufferstates):
1531 out = self
1543 out = self
1532 with self.timeblockedsection(blockedtag):
1544 with self.timeblockedsection(blockedtag):
1533 rc = self._runsystem(cmd, environ=environ, cwd=cwd, out=out)
1545 rc = self._runsystem(cmd, environ=environ, cwd=cwd, out=out)
1534 if rc and onerr:
1546 if rc and onerr:
1535 errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]),
1547 errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]),
1536 procutil.explainexit(rc))
1548 procutil.explainexit(rc))
1537 if errprefix:
1549 if errprefix:
1538 errmsg = '%s: %s' % (errprefix, errmsg)
1550 errmsg = '%s: %s' % (errprefix, errmsg)
1539 raise onerr(errmsg)
1551 raise onerr(errmsg)
1540 return rc
1552 return rc
1541
1553
1542 def _runsystem(self, cmd, environ, cwd, out):
1554 def _runsystem(self, cmd, environ, cwd, out):
1543 """actually execute the given shell command (can be overridden by
1555 """actually execute the given shell command (can be overridden by
1544 extensions like chg)"""
1556 extensions like chg)"""
1545 return procutil.system(cmd, environ=environ, cwd=cwd, out=out)
1557 return procutil.system(cmd, environ=environ, cwd=cwd, out=out)
1546
1558
1547 def traceback(self, exc=None, force=False):
1559 def traceback(self, exc=None, force=False):
1548 '''print exception traceback if traceback printing enabled or forced.
1560 '''print exception traceback if traceback printing enabled or forced.
1549 only to call in exception handler. returns true if traceback
1561 only to call in exception handler. returns true if traceback
1550 printed.'''
1562 printed.'''
1551 if self.tracebackflag or force:
1563 if self.tracebackflag or force:
1552 if exc is None:
1564 if exc is None:
1553 exc = sys.exc_info()
1565 exc = sys.exc_info()
1554 cause = getattr(exc[1], 'cause', None)
1566 cause = getattr(exc[1], 'cause', None)
1555
1567
1556 if cause is not None:
1568 if cause is not None:
1557 causetb = traceback.format_tb(cause[2])
1569 causetb = traceback.format_tb(cause[2])
1558 exctb = traceback.format_tb(exc[2])
1570 exctb = traceback.format_tb(exc[2])
1559 exconly = traceback.format_exception_only(cause[0], cause[1])
1571 exconly = traceback.format_exception_only(cause[0], cause[1])
1560
1572
1561 # exclude frame where 'exc' was chained and rethrown from exctb
1573 # exclude frame where 'exc' was chained and rethrown from exctb
1562 self.write_err('Traceback (most recent call last):\n',
1574 self.write_err('Traceback (most recent call last):\n',
1563 ''.join(exctb[:-1]),
1575 ''.join(exctb[:-1]),
1564 ''.join(causetb),
1576 ''.join(causetb),
1565 ''.join(exconly))
1577 ''.join(exconly))
1566 else:
1578 else:
1567 output = traceback.format_exception(exc[0], exc[1], exc[2])
1579 output = traceback.format_exception(exc[0], exc[1], exc[2])
1568 self.write_err(encoding.strtolocal(r''.join(output)))
1580 self.write_err(encoding.strtolocal(r''.join(output)))
1569 return self.tracebackflag or force
1581 return self.tracebackflag or force
1570
1582
1571 def geteditor(self):
1583 def geteditor(self):
1572 '''return editor to use'''
1584 '''return editor to use'''
1573 if pycompat.sysplatform == 'plan9':
1585 if pycompat.sysplatform == 'plan9':
1574 # vi is the MIPS instruction simulator on Plan 9. We
1586 # vi is the MIPS instruction simulator on Plan 9. We
1575 # instead default to E to plumb commit messages to
1587 # instead default to E to plumb commit messages to
1576 # avoid confusion.
1588 # avoid confusion.
1577 editor = 'E'
1589 editor = 'E'
1578 else:
1590 else:
1579 editor = 'vi'
1591 editor = 'vi'
1580 return (encoding.environ.get("HGEDITOR") or
1592 return (encoding.environ.get("HGEDITOR") or
1581 self.config("ui", "editor", editor))
1593 self.config("ui", "editor", editor))
1582
1594
1583 @util.propertycache
1595 @util.propertycache
1584 def _progbar(self):
1596 def _progbar(self):
1585 """setup the progbar singleton to the ui object"""
1597 """setup the progbar singleton to the ui object"""
1586 if (self.quiet or self.debugflag
1598 if (self.quiet or self.debugflag
1587 or self.configbool('progress', 'disable')
1599 or self.configbool('progress', 'disable')
1588 or not progress.shouldprint(self)):
1600 or not progress.shouldprint(self)):
1589 return None
1601 return None
1590 return getprogbar(self)
1602 return getprogbar(self)
1591
1603
1592 def _progclear(self):
1604 def _progclear(self):
1593 """clear progress bar output if any. use it before any output"""
1605 """clear progress bar output if any. use it before any output"""
1594 if not haveprogbar(): # nothing loaded yet
1606 if not haveprogbar(): # nothing loaded yet
1595 return
1607 return
1596 if self._progbar is not None and self._progbar.printed:
1608 if self._progbar is not None and self._progbar.printed:
1597 self._progbar.clear()
1609 self._progbar.clear()
1598
1610
1599 def progress(self, topic, pos, item="", unit="", total=None):
1611 def progress(self, topic, pos, item="", unit="", total=None):
1600 '''show a progress message
1612 '''show a progress message
1601
1613
1602 By default a textual progress bar will be displayed if an operation
1614 By default a textual progress bar will be displayed if an operation
1603 takes too long. 'topic' is the current operation, 'item' is a
1615 takes too long. 'topic' is the current operation, 'item' is a
1604 non-numeric marker of the current position (i.e. the currently
1616 non-numeric marker of the current position (i.e. the currently
1605 in-process file), 'pos' is the current numeric position (i.e.
1617 in-process file), 'pos' is the current numeric position (i.e.
1606 revision, bytes, etc.), unit is a corresponding unit label,
1618 revision, bytes, etc.), unit is a corresponding unit label,
1607 and total is the highest expected pos.
1619 and total is the highest expected pos.
1608
1620
1609 Multiple nested topics may be active at a time.
1621 Multiple nested topics may be active at a time.
1610
1622
1611 All topics should be marked closed by setting pos to None at
1623 All topics should be marked closed by setting pos to None at
1612 termination.
1624 termination.
1613 '''
1625 '''
1614 if self._progbar is not None:
1626 if self._progbar is not None:
1615 self._progbar.progress(topic, pos, item=item, unit=unit,
1627 self._progbar.progress(topic, pos, item=item, unit=unit,
1616 total=total)
1628 total=total)
1617 if pos is None or not self.configbool('progress', 'debug'):
1629 if pos is None or not self.configbool('progress', 'debug'):
1618 return
1630 return
1619
1631
1620 if unit:
1632 if unit:
1621 unit = ' ' + unit
1633 unit = ' ' + unit
1622 if item:
1634 if item:
1623 item = ' ' + item
1635 item = ' ' + item
1624
1636
1625 if total:
1637 if total:
1626 pct = 100.0 * pos / total
1638 pct = 100.0 * pos / total
1627 self.debug('%s:%s %d/%d%s (%4.2f%%)\n'
1639 self.debug('%s:%s %d/%d%s (%4.2f%%)\n'
1628 % (topic, item, pos, total, unit, pct))
1640 % (topic, item, pos, total, unit, pct))
1629 else:
1641 else:
1630 self.debug('%s:%s %d%s\n' % (topic, item, pos, unit))
1642 self.debug('%s:%s %d%s\n' % (topic, item, pos, unit))
1631
1643
1632 def makeprogress(self, topic, unit="", total=None):
1644 def makeprogress(self, topic, unit="", total=None):
1633 '''exists only so low-level modules won't need to import scmutil'''
1645 '''exists only so low-level modules won't need to import scmutil'''
1634 return scmutil.progress(self, topic, unit, total)
1646 return scmutil.progress(self, topic, unit, total)
1635
1647
1636 def log(self, service, *msg, **opts):
1648 def log(self, service, *msg, **opts):
1637 '''hook for logging facility extensions
1649 '''hook for logging facility extensions
1638
1650
1639 service should be a readily-identifiable subsystem, which will
1651 service should be a readily-identifiable subsystem, which will
1640 allow filtering.
1652 allow filtering.
1641
1653
1642 *msg should be a newline-terminated format string to log, and
1654 *msg should be a newline-terminated format string to log, and
1643 then any values to %-format into that format string.
1655 then any values to %-format into that format string.
1644
1656
1645 **opts currently has no defined meanings.
1657 **opts currently has no defined meanings.
1646 '''
1658 '''
1647
1659
1648 def label(self, msg, label):
1660 def label(self, msg, label):
1649 '''style msg based on supplied label
1661 '''style msg based on supplied label
1650
1662
1651 If some color mode is enabled, this will add the necessary control
1663 If some color mode is enabled, this will add the necessary control
1652 characters to apply such color. In addition, 'debug' color mode adds
1664 characters to apply such color. In addition, 'debug' color mode adds
1653 markup showing which label affects a piece of text.
1665 markup showing which label affects a piece of text.
1654
1666
1655 ui.write(s, 'label') is equivalent to
1667 ui.write(s, 'label') is equivalent to
1656 ui.write(ui.label(s, 'label')).
1668 ui.write(ui.label(s, 'label')).
1657 '''
1669 '''
1658 if self._colormode is not None:
1670 if self._colormode is not None:
1659 return color.colorlabel(self, msg, label)
1671 return color.colorlabel(self, msg, label)
1660 return msg
1672 return msg
1661
1673
1662 def develwarn(self, msg, stacklevel=1, config=None):
1674 def develwarn(self, msg, stacklevel=1, config=None):
1663 """issue a developer warning message
1675 """issue a developer warning message
1664
1676
1665 Use 'stacklevel' to report the offender some layers further up in the
1677 Use 'stacklevel' to report the offender some layers further up in the
1666 stack.
1678 stack.
1667 """
1679 """
1668 if not self.configbool('devel', 'all-warnings'):
1680 if not self.configbool('devel', 'all-warnings'):
1669 if config is None or not self.configbool('devel', config):
1681 if config is None or not self.configbool('devel', config):
1670 return
1682 return
1671 msg = 'devel-warn: ' + msg
1683 msg = 'devel-warn: ' + msg
1672 stacklevel += 1 # get in develwarn
1684 stacklevel += 1 # get in develwarn
1673 if self.tracebackflag:
1685 if self.tracebackflag:
1674 util.debugstacktrace(msg, stacklevel, self.ferr, self.fout)
1686 util.debugstacktrace(msg, stacklevel, self.ferr, self.fout)
1675 self.log('develwarn', '%s at:\n%s' %
1687 self.log('develwarn', '%s at:\n%s' %
1676 (msg, ''.join(util.getstackframes(stacklevel))))
1688 (msg, ''.join(util.getstackframes(stacklevel))))
1677 else:
1689 else:
1678 curframe = inspect.currentframe()
1690 curframe = inspect.currentframe()
1679 calframe = inspect.getouterframes(curframe, 2)
1691 calframe = inspect.getouterframes(curframe, 2)
1680 fname, lineno, fmsg = calframe[stacklevel][1:4]
1692 fname, lineno, fmsg = calframe[stacklevel][1:4]
1681 fname, fmsg = pycompat.sysbytes(fname), pycompat.sysbytes(fmsg)
1693 fname, fmsg = pycompat.sysbytes(fname), pycompat.sysbytes(fmsg)
1682 self.write_err('%s at: %s:%d (%s)\n'
1694 self.write_err('%s at: %s:%d (%s)\n'
1683 % (msg, fname, lineno, fmsg))
1695 % (msg, fname, lineno, fmsg))
1684 self.log('develwarn', '%s at: %s:%d (%s)\n',
1696 self.log('develwarn', '%s at: %s:%d (%s)\n',
1685 msg, fname, lineno, fmsg)
1697 msg, fname, lineno, fmsg)
1686 curframe = calframe = None # avoid cycles
1698 curframe = calframe = None # avoid cycles
1687
1699
1688 def deprecwarn(self, msg, version, stacklevel=2):
1700 def deprecwarn(self, msg, version, stacklevel=2):
1689 """issue a deprecation warning
1701 """issue a deprecation warning
1690
1702
1691 - msg: message explaining what is deprecated and how to upgrade,
1703 - msg: message explaining what is deprecated and how to upgrade,
1692 - version: last version where the API will be supported,
1704 - version: last version where the API will be supported,
1693 """
1705 """
1694 if not (self.configbool('devel', 'all-warnings')
1706 if not (self.configbool('devel', 'all-warnings')
1695 or self.configbool('devel', 'deprec-warn')):
1707 or self.configbool('devel', 'deprec-warn')):
1696 return
1708 return
1697 msg += ("\n(compatibility will be dropped after Mercurial-%s,"
1709 msg += ("\n(compatibility will be dropped after Mercurial-%s,"
1698 " update your code.)") % version
1710 " update your code.)") % version
1699 self.develwarn(msg, stacklevel=stacklevel, config='deprec-warn')
1711 self.develwarn(msg, stacklevel=stacklevel, config='deprec-warn')
1700
1712
1701 def exportableenviron(self):
1713 def exportableenviron(self):
1702 """The environment variables that are safe to export, e.g. through
1714 """The environment variables that are safe to export, e.g. through
1703 hgweb.
1715 hgweb.
1704 """
1716 """
1705 return self._exportableenviron
1717 return self._exportableenviron
1706
1718
1707 @contextlib.contextmanager
1719 @contextlib.contextmanager
1708 def configoverride(self, overrides, source=""):
1720 def configoverride(self, overrides, source=""):
1709 """Context manager for temporary config overrides
1721 """Context manager for temporary config overrides
1710 `overrides` must be a dict of the following structure:
1722 `overrides` must be a dict of the following structure:
1711 {(section, name) : value}"""
1723 {(section, name) : value}"""
1712 backups = {}
1724 backups = {}
1713 try:
1725 try:
1714 for (section, name), value in overrides.items():
1726 for (section, name), value in overrides.items():
1715 backups[(section, name)] = self.backupconfig(section, name)
1727 backups[(section, name)] = self.backupconfig(section, name)
1716 self.setconfig(section, name, value, source)
1728 self.setconfig(section, name, value, source)
1717 yield
1729 yield
1718 finally:
1730 finally:
1719 for __, backup in backups.items():
1731 for __, backup in backups.items():
1720 self.restoreconfig(backup)
1732 self.restoreconfig(backup)
1721 # just restoring ui.quiet config to the previous value is not enough
1733 # just restoring ui.quiet config to the previous value is not enough
1722 # as it does not update ui.quiet class member
1734 # as it does not update ui.quiet class member
1723 if ('ui', 'quiet') in overrides:
1735 if ('ui', 'quiet') in overrides:
1724 self.fixconfig(section='ui')
1736 self.fixconfig(section='ui')
1725
1737
1726 class paths(dict):
1738 class paths(dict):
1727 """Represents a collection of paths and their configs.
1739 """Represents a collection of paths and their configs.
1728
1740
1729 Data is initially derived from ui instances and the config files they have
1741 Data is initially derived from ui instances and the config files they have
1730 loaded.
1742 loaded.
1731 """
1743 """
1732 def __init__(self, ui):
1744 def __init__(self, ui):
1733 dict.__init__(self)
1745 dict.__init__(self)
1734
1746
1735 for name, loc in ui.configitems('paths', ignoresub=True):
1747 for name, loc in ui.configitems('paths', ignoresub=True):
1736 # No location is the same as not existing.
1748 # No location is the same as not existing.
1737 if not loc:
1749 if not loc:
1738 continue
1750 continue
1739 loc, sub = ui.configsuboptions('paths', name)
1751 loc, sub = ui.configsuboptions('paths', name)
1740 self[name] = path(ui, name, rawloc=loc, suboptions=sub)
1752 self[name] = path(ui, name, rawloc=loc, suboptions=sub)
1741
1753
1742 def getpath(self, name, default=None):
1754 def getpath(self, name, default=None):
1743 """Return a ``path`` from a string, falling back to default.
1755 """Return a ``path`` from a string, falling back to default.
1744
1756
1745 ``name`` can be a named path or locations. Locations are filesystem
1757 ``name`` can be a named path or locations. Locations are filesystem
1746 paths or URIs.
1758 paths or URIs.
1747
1759
1748 Returns None if ``name`` is not a registered path, a URI, or a local
1760 Returns None if ``name`` is not a registered path, a URI, or a local
1749 path to a repo.
1761 path to a repo.
1750 """
1762 """
1751 # Only fall back to default if no path was requested.
1763 # Only fall back to default if no path was requested.
1752 if name is None:
1764 if name is None:
1753 if not default:
1765 if not default:
1754 default = ()
1766 default = ()
1755 elif not isinstance(default, (tuple, list)):
1767 elif not isinstance(default, (tuple, list)):
1756 default = (default,)
1768 default = (default,)
1757 for k in default:
1769 for k in default:
1758 try:
1770 try:
1759 return self[k]
1771 return self[k]
1760 except KeyError:
1772 except KeyError:
1761 continue
1773 continue
1762 return None
1774 return None
1763
1775
1764 # Most likely empty string.
1776 # Most likely empty string.
1765 # This may need to raise in the future.
1777 # This may need to raise in the future.
1766 if not name:
1778 if not name:
1767 return None
1779 return None
1768
1780
1769 try:
1781 try:
1770 return self[name]
1782 return self[name]
1771 except KeyError:
1783 except KeyError:
1772 # Try to resolve as a local path or URI.
1784 # Try to resolve as a local path or URI.
1773 try:
1785 try:
1774 # We don't pass sub-options in, so no need to pass ui instance.
1786 # We don't pass sub-options in, so no need to pass ui instance.
1775 return path(None, None, rawloc=name)
1787 return path(None, None, rawloc=name)
1776 except ValueError:
1788 except ValueError:
1777 raise error.RepoError(_('repository %s does not exist') %
1789 raise error.RepoError(_('repository %s does not exist') %
1778 name)
1790 name)
1779
1791
1780 _pathsuboptions = {}
1792 _pathsuboptions = {}
1781
1793
1782 def pathsuboption(option, attr):
1794 def pathsuboption(option, attr):
1783 """Decorator used to declare a path sub-option.
1795 """Decorator used to declare a path sub-option.
1784
1796
1785 Arguments are the sub-option name and the attribute it should set on
1797 Arguments are the sub-option name and the attribute it should set on
1786 ``path`` instances.
1798 ``path`` instances.
1787
1799
1788 The decorated function will receive as arguments a ``ui`` instance,
1800 The decorated function will receive as arguments a ``ui`` instance,
1789 ``path`` instance, and the string value of this option from the config.
1801 ``path`` instance, and the string value of this option from the config.
1790 The function should return the value that will be set on the ``path``
1802 The function should return the value that will be set on the ``path``
1791 instance.
1803 instance.
1792
1804
1793 This decorator can be used to perform additional verification of
1805 This decorator can be used to perform additional verification of
1794 sub-options and to change the type of sub-options.
1806 sub-options and to change the type of sub-options.
1795 """
1807 """
1796 def register(func):
1808 def register(func):
1797 _pathsuboptions[option] = (attr, func)
1809 _pathsuboptions[option] = (attr, func)
1798 return func
1810 return func
1799 return register
1811 return register
1800
1812
1801 @pathsuboption('pushurl', 'pushloc')
1813 @pathsuboption('pushurl', 'pushloc')
1802 def pushurlpathoption(ui, path, value):
1814 def pushurlpathoption(ui, path, value):
1803 u = util.url(value)
1815 u = util.url(value)
1804 # Actually require a URL.
1816 # Actually require a URL.
1805 if not u.scheme:
1817 if not u.scheme:
1806 ui.warn(_('(paths.%s:pushurl not a URL; ignoring)\n') % path.name)
1818 ui.warn(_('(paths.%s:pushurl not a URL; ignoring)\n') % path.name)
1807 return None
1819 return None
1808
1820
1809 # Don't support the #foo syntax in the push URL to declare branch to
1821 # Don't support the #foo syntax in the push URL to declare branch to
1810 # push.
1822 # push.
1811 if u.fragment:
1823 if u.fragment:
1812 ui.warn(_('("#fragment" in paths.%s:pushurl not supported; '
1824 ui.warn(_('("#fragment" in paths.%s:pushurl not supported; '
1813 'ignoring)\n') % path.name)
1825 'ignoring)\n') % path.name)
1814 u.fragment = None
1826 u.fragment = None
1815
1827
1816 return bytes(u)
1828 return bytes(u)
1817
1829
1818 @pathsuboption('pushrev', 'pushrev')
1830 @pathsuboption('pushrev', 'pushrev')
1819 def pushrevpathoption(ui, path, value):
1831 def pushrevpathoption(ui, path, value):
1820 return value
1832 return value
1821
1833
1822 class path(object):
1834 class path(object):
1823 """Represents an individual path and its configuration."""
1835 """Represents an individual path and its configuration."""
1824
1836
1825 def __init__(self, ui, name, rawloc=None, suboptions=None):
1837 def __init__(self, ui, name, rawloc=None, suboptions=None):
1826 """Construct a path from its config options.
1838 """Construct a path from its config options.
1827
1839
1828 ``ui`` is the ``ui`` instance the path is coming from.
1840 ``ui`` is the ``ui`` instance the path is coming from.
1829 ``name`` is the symbolic name of the path.
1841 ``name`` is the symbolic name of the path.
1830 ``rawloc`` is the raw location, as defined in the config.
1842 ``rawloc`` is the raw location, as defined in the config.
1831 ``pushloc`` is the raw locations pushes should be made to.
1843 ``pushloc`` is the raw locations pushes should be made to.
1832
1844
1833 If ``name`` is not defined, we require that the location be a) a local
1845 If ``name`` is not defined, we require that the location be a) a local
1834 filesystem path with a .hg directory or b) a URL. If not,
1846 filesystem path with a .hg directory or b) a URL. If not,
1835 ``ValueError`` is raised.
1847 ``ValueError`` is raised.
1836 """
1848 """
1837 if not rawloc:
1849 if not rawloc:
1838 raise ValueError('rawloc must be defined')
1850 raise ValueError('rawloc must be defined')
1839
1851
1840 # Locations may define branches via syntax <base>#<branch>.
1852 # Locations may define branches via syntax <base>#<branch>.
1841 u = util.url(rawloc)
1853 u = util.url(rawloc)
1842 branch = None
1854 branch = None
1843 if u.fragment:
1855 if u.fragment:
1844 branch = u.fragment
1856 branch = u.fragment
1845 u.fragment = None
1857 u.fragment = None
1846
1858
1847 self.url = u
1859 self.url = u
1848 self.branch = branch
1860 self.branch = branch
1849
1861
1850 self.name = name
1862 self.name = name
1851 self.rawloc = rawloc
1863 self.rawloc = rawloc
1852 self.loc = '%s' % u
1864 self.loc = '%s' % u
1853
1865
1854 # When given a raw location but not a symbolic name, validate the
1866 # When given a raw location but not a symbolic name, validate the
1855 # location is valid.
1867 # location is valid.
1856 if not name and not u.scheme and not self._isvalidlocalpath(self.loc):
1868 if not name and not u.scheme and not self._isvalidlocalpath(self.loc):
1857 raise ValueError('location is not a URL or path to a local '
1869 raise ValueError('location is not a URL or path to a local '
1858 'repo: %s' % rawloc)
1870 'repo: %s' % rawloc)
1859
1871
1860 suboptions = suboptions or {}
1872 suboptions = suboptions or {}
1861
1873
1862 # Now process the sub-options. If a sub-option is registered, its
1874 # Now process the sub-options. If a sub-option is registered, its
1863 # attribute will always be present. The value will be None if there
1875 # attribute will always be present. The value will be None if there
1864 # was no valid sub-option.
1876 # was no valid sub-option.
1865 for suboption, (attr, func) in _pathsuboptions.iteritems():
1877 for suboption, (attr, func) in _pathsuboptions.iteritems():
1866 if suboption not in suboptions:
1878 if suboption not in suboptions:
1867 setattr(self, attr, None)
1879 setattr(self, attr, None)
1868 continue
1880 continue
1869
1881
1870 value = func(ui, self, suboptions[suboption])
1882 value = func(ui, self, suboptions[suboption])
1871 setattr(self, attr, value)
1883 setattr(self, attr, value)
1872
1884
1873 def _isvalidlocalpath(self, path):
1885 def _isvalidlocalpath(self, path):
1874 """Returns True if the given path is a potentially valid repository.
1886 """Returns True if the given path is a potentially valid repository.
1875 This is its own function so that extensions can change the definition of
1887 This is its own function so that extensions can change the definition of
1876 'valid' in this case (like when pulling from a git repo into a hg
1888 'valid' in this case (like when pulling from a git repo into a hg
1877 one)."""
1889 one)."""
1878 return os.path.isdir(os.path.join(path, '.hg'))
1890 return os.path.isdir(os.path.join(path, '.hg'))
1879
1891
1880 @property
1892 @property
1881 def suboptions(self):
1893 def suboptions(self):
1882 """Return sub-options and their values for this path.
1894 """Return sub-options and their values for this path.
1883
1895
1884 This is intended to be used for presentation purposes.
1896 This is intended to be used for presentation purposes.
1885 """
1897 """
1886 d = {}
1898 d = {}
1887 for subopt, (attr, _func) in _pathsuboptions.iteritems():
1899 for subopt, (attr, _func) in _pathsuboptions.iteritems():
1888 value = getattr(self, attr)
1900 value = getattr(self, attr)
1889 if value is not None:
1901 if value is not None:
1890 d[subopt] = value
1902 d[subopt] = value
1891 return d
1903 return d
1892
1904
1893 # we instantiate one globally shared progress bar to avoid
1905 # we instantiate one globally shared progress bar to avoid
1894 # competing progress bars when multiple UI objects get created
1906 # competing progress bars when multiple UI objects get created
1895 _progresssingleton = None
1907 _progresssingleton = None
1896
1908
1897 def getprogbar(ui):
1909 def getprogbar(ui):
1898 global _progresssingleton
1910 global _progresssingleton
1899 if _progresssingleton is None:
1911 if _progresssingleton is None:
1900 # passing 'ui' object to the singleton is fishy,
1912 # passing 'ui' object to the singleton is fishy,
1901 # this is how the extension used to work but feel free to rework it.
1913 # this is how the extension used to work but feel free to rework it.
1902 _progresssingleton = progress.progbar(ui)
1914 _progresssingleton = progress.progbar(ui)
1903 return _progresssingleton
1915 return _progresssingleton
1904
1916
1905 def haveprogbar():
1917 def haveprogbar():
1906 return _progresssingleton is not None
1918 return _progresssingleton is not None
General Comments 0
You need to be logged in to leave comments. Login now