##// END OF EJS Templates
dispatch: handle IOError when writing to stderr...
Gregory Szorc -
r35671:48fe4f56 default
parent child Browse files
Show More
@@ -1,996 +1,1002 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 registrar,
38 registrar,
39 scmutil,
39 scmutil,
40 ui as uimod,
40 ui as uimod,
41 util,
41 util,
42 )
42 )
43
43
44 unrecoverablewrite = registrar.command.unrecoverablewrite
44 unrecoverablewrite = registrar.command.unrecoverablewrite
45
45
46 class request(object):
46 class request(object):
47 def __init__(self, args, ui=None, repo=None, fin=None, fout=None,
47 def __init__(self, args, ui=None, repo=None, fin=None, fout=None,
48 ferr=None, prereposetups=None):
48 ferr=None, prereposetups=None):
49 self.args = args
49 self.args = args
50 self.ui = ui
50 self.ui = ui
51 self.repo = repo
51 self.repo = repo
52
52
53 # input/output/error streams
53 # input/output/error streams
54 self.fin = fin
54 self.fin = fin
55 self.fout = fout
55 self.fout = fout
56 self.ferr = ferr
56 self.ferr = ferr
57
57
58 # remember options pre-parsed by _earlyparseopts()
58 # remember options pre-parsed by _earlyparseopts()
59 self.earlyoptions = {}
59 self.earlyoptions = {}
60
60
61 # reposetups which run before extensions, useful for chg to pre-fill
61 # reposetups which run before extensions, useful for chg to pre-fill
62 # low-level repo state (for example, changelog) before extensions.
62 # low-level repo state (for example, changelog) before extensions.
63 self.prereposetups = prereposetups or []
63 self.prereposetups = prereposetups or []
64
64
65 def _runexithandlers(self):
65 def _runexithandlers(self):
66 exc = None
66 exc = None
67 handlers = self.ui._exithandlers
67 handlers = self.ui._exithandlers
68 try:
68 try:
69 while handlers:
69 while handlers:
70 func, args, kwargs = handlers.pop()
70 func, args, kwargs = handlers.pop()
71 try:
71 try:
72 func(*args, **kwargs)
72 func(*args, **kwargs)
73 except: # re-raises below
73 except: # re-raises below
74 if exc is None:
74 if exc is None:
75 exc = sys.exc_info()[1]
75 exc = sys.exc_info()[1]
76 self.ui.warn(('error in exit handlers:\n'))
76 self.ui.warn(('error in exit handlers:\n'))
77 self.ui.traceback(force=True)
77 self.ui.traceback(force=True)
78 finally:
78 finally:
79 if exc is not None:
79 if exc is not None:
80 raise exc
80 raise exc
81
81
82 def run():
82 def run():
83 "run the command in sys.argv"
83 "run the command in sys.argv"
84 _initstdio()
84 _initstdio()
85 req = request(pycompat.sysargv[1:])
85 req = request(pycompat.sysargv[1:])
86 err = None
86 err = None
87 try:
87 try:
88 status = (dispatch(req) or 0) & 255
88 status = (dispatch(req) or 0) & 255
89 except error.StdioError as e:
89 except error.StdioError as e:
90 err = e
90 err = e
91 status = -1
91 status = -1
92 if util.safehasattr(req.ui, 'fout'):
92 if util.safehasattr(req.ui, 'fout'):
93 try:
93 try:
94 req.ui.fout.flush()
94 req.ui.fout.flush()
95 except IOError as e:
95 except IOError as e:
96 err = e
96 err = e
97 status = -1
97 status = -1
98 if util.safehasattr(req.ui, 'ferr'):
98 if util.safehasattr(req.ui, 'ferr'):
99 if err is not None and err.errno != errno.EPIPE:
99 try:
100 req.ui.ferr.write('abort: %s\n' %
100 if err is not None and err.errno != errno.EPIPE:
101 encoding.strtolocal(err.strerror))
101 req.ui.ferr.write('abort: %s\n' %
102 req.ui.ferr.flush()
102 encoding.strtolocal(err.strerror))
103 req.ui.ferr.flush()
104 # There's not much we can do about an I/O error here. So (possibly)
105 # change the status code and move on.
106 except IOError:
107 status = -1
108
103 sys.exit(status & 255)
109 sys.exit(status & 255)
104
110
105 def _initstdio():
111 def _initstdio():
106 for fp in (sys.stdin, sys.stdout, sys.stderr):
112 for fp in (sys.stdin, sys.stdout, sys.stderr):
107 util.setbinary(fp)
113 util.setbinary(fp)
108
114
109 def _getsimilar(symbols, value):
115 def _getsimilar(symbols, value):
110 sim = lambda x: difflib.SequenceMatcher(None, value, x).ratio()
116 sim = lambda x: difflib.SequenceMatcher(None, value, x).ratio()
111 # The cutoff for similarity here is pretty arbitrary. It should
117 # The cutoff for similarity here is pretty arbitrary. It should
112 # probably be investigated and tweaked.
118 # probably be investigated and tweaked.
113 return [s for s in symbols if sim(s) > 0.6]
119 return [s for s in symbols if sim(s) > 0.6]
114
120
115 def _reportsimilar(write, similar):
121 def _reportsimilar(write, similar):
116 if len(similar) == 1:
122 if len(similar) == 1:
117 write(_("(did you mean %s?)\n") % similar[0])
123 write(_("(did you mean %s?)\n") % similar[0])
118 elif similar:
124 elif similar:
119 ss = ", ".join(sorted(similar))
125 ss = ", ".join(sorted(similar))
120 write(_("(did you mean one of %s?)\n") % ss)
126 write(_("(did you mean one of %s?)\n") % ss)
121
127
122 def _formatparse(write, inst):
128 def _formatparse(write, inst):
123 similar = []
129 similar = []
124 if isinstance(inst, error.UnknownIdentifier):
130 if isinstance(inst, error.UnknownIdentifier):
125 # make sure to check fileset first, as revset can invoke fileset
131 # make sure to check fileset first, as revset can invoke fileset
126 similar = _getsimilar(inst.symbols, inst.function)
132 similar = _getsimilar(inst.symbols, inst.function)
127 if len(inst.args) > 1:
133 if len(inst.args) > 1:
128 write(_("hg: parse error at %s: %s\n") %
134 write(_("hg: parse error at %s: %s\n") %
129 (inst.args[1], inst.args[0]))
135 (inst.args[1], inst.args[0]))
130 if (inst.args[0][0] == ' '):
136 if (inst.args[0][0] == ' '):
131 write(_("unexpected leading whitespace\n"))
137 write(_("unexpected leading whitespace\n"))
132 else:
138 else:
133 write(_("hg: parse error: %s\n") % inst.args[0])
139 write(_("hg: parse error: %s\n") % inst.args[0])
134 _reportsimilar(write, similar)
140 _reportsimilar(write, similar)
135 if inst.hint:
141 if inst.hint:
136 write(_("(%s)\n") % inst.hint)
142 write(_("(%s)\n") % inst.hint)
137
143
138 def _formatargs(args):
144 def _formatargs(args):
139 return ' '.join(util.shellquote(a) for a in args)
145 return ' '.join(util.shellquote(a) for a in args)
140
146
141 def dispatch(req):
147 def dispatch(req):
142 "run the command specified in req.args"
148 "run the command specified in req.args"
143 if req.ferr:
149 if req.ferr:
144 ferr = req.ferr
150 ferr = req.ferr
145 elif req.ui:
151 elif req.ui:
146 ferr = req.ui.ferr
152 ferr = req.ui.ferr
147 else:
153 else:
148 ferr = util.stderr
154 ferr = util.stderr
149
155
150 try:
156 try:
151 if not req.ui:
157 if not req.ui:
152 req.ui = uimod.ui.load()
158 req.ui = uimod.ui.load()
153 req.earlyoptions.update(_earlyparseopts(req.ui, req.args))
159 req.earlyoptions.update(_earlyparseopts(req.ui, req.args))
154 if req.earlyoptions['traceback']:
160 if req.earlyoptions['traceback']:
155 req.ui.setconfig('ui', 'traceback', 'on', '--traceback')
161 req.ui.setconfig('ui', 'traceback', 'on', '--traceback')
156
162
157 # set ui streams from the request
163 # set ui streams from the request
158 if req.fin:
164 if req.fin:
159 req.ui.fin = req.fin
165 req.ui.fin = req.fin
160 if req.fout:
166 if req.fout:
161 req.ui.fout = req.fout
167 req.ui.fout = req.fout
162 if req.ferr:
168 if req.ferr:
163 req.ui.ferr = req.ferr
169 req.ui.ferr = req.ferr
164 except error.Abort as inst:
170 except error.Abort as inst:
165 ferr.write(_("abort: %s\n") % inst)
171 ferr.write(_("abort: %s\n") % inst)
166 if inst.hint:
172 if inst.hint:
167 ferr.write(_("(%s)\n") % inst.hint)
173 ferr.write(_("(%s)\n") % inst.hint)
168 return -1
174 return -1
169 except error.ParseError as inst:
175 except error.ParseError as inst:
170 _formatparse(ferr.write, inst)
176 _formatparse(ferr.write, inst)
171 return -1
177 return -1
172
178
173 msg = _formatargs(req.args)
179 msg = _formatargs(req.args)
174 starttime = util.timer()
180 starttime = util.timer()
175 ret = None
181 ret = None
176 try:
182 try:
177 ret = _runcatch(req)
183 ret = _runcatch(req)
178 except error.ProgrammingError as inst:
184 except error.ProgrammingError as inst:
179 req.ui.warn(_('** ProgrammingError: %s\n') % inst)
185 req.ui.warn(_('** ProgrammingError: %s\n') % inst)
180 if inst.hint:
186 if inst.hint:
181 req.ui.warn(_('** (%s)\n') % inst.hint)
187 req.ui.warn(_('** (%s)\n') % inst.hint)
182 raise
188 raise
183 except KeyboardInterrupt as inst:
189 except KeyboardInterrupt as inst:
184 try:
190 try:
185 if isinstance(inst, error.SignalInterrupt):
191 if isinstance(inst, error.SignalInterrupt):
186 msg = _("killed!\n")
192 msg = _("killed!\n")
187 else:
193 else:
188 msg = _("interrupted!\n")
194 msg = _("interrupted!\n")
189 req.ui.warn(msg)
195 req.ui.warn(msg)
190 except error.SignalInterrupt:
196 except error.SignalInterrupt:
191 # maybe pager would quit without consuming all the output, and
197 # maybe pager would quit without consuming all the output, and
192 # SIGPIPE was raised. we cannot print anything in this case.
198 # SIGPIPE was raised. we cannot print anything in this case.
193 pass
199 pass
194 except IOError as inst:
200 except IOError as inst:
195 if inst.errno != errno.EPIPE:
201 if inst.errno != errno.EPIPE:
196 raise
202 raise
197 ret = -1
203 ret = -1
198 finally:
204 finally:
199 duration = util.timer() - starttime
205 duration = util.timer() - starttime
200 req.ui.flush()
206 req.ui.flush()
201 if req.ui.logblockedtimes:
207 if req.ui.logblockedtimes:
202 req.ui._blockedtimes['command_duration'] = duration * 1000
208 req.ui._blockedtimes['command_duration'] = duration * 1000
203 req.ui.log('uiblocked', 'ui blocked ms',
209 req.ui.log('uiblocked', 'ui blocked ms',
204 **pycompat.strkwargs(req.ui._blockedtimes))
210 **pycompat.strkwargs(req.ui._blockedtimes))
205 req.ui.log("commandfinish", "%s exited %d after %0.2f seconds\n",
211 req.ui.log("commandfinish", "%s exited %d after %0.2f seconds\n",
206 msg, ret or 0, duration)
212 msg, ret or 0, duration)
207 try:
213 try:
208 req._runexithandlers()
214 req._runexithandlers()
209 except: # exiting, so no re-raises
215 except: # exiting, so no re-raises
210 ret = ret or -1
216 ret = ret or -1
211 return ret
217 return ret
212
218
213 def _runcatch(req):
219 def _runcatch(req):
214 def catchterm(*args):
220 def catchterm(*args):
215 raise error.SignalInterrupt
221 raise error.SignalInterrupt
216
222
217 ui = req.ui
223 ui = req.ui
218 try:
224 try:
219 for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM':
225 for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM':
220 num = getattr(signal, name, None)
226 num = getattr(signal, name, None)
221 if num:
227 if num:
222 signal.signal(num, catchterm)
228 signal.signal(num, catchterm)
223 except ValueError:
229 except ValueError:
224 pass # happens if called in a thread
230 pass # happens if called in a thread
225
231
226 def _runcatchfunc():
232 def _runcatchfunc():
227 realcmd = None
233 realcmd = None
228 try:
234 try:
229 cmdargs = fancyopts.fancyopts(req.args[:], commands.globalopts, {})
235 cmdargs = fancyopts.fancyopts(req.args[:], commands.globalopts, {})
230 cmd = cmdargs[0]
236 cmd = cmdargs[0]
231 aliases, entry = cmdutil.findcmd(cmd, commands.table, False)
237 aliases, entry = cmdutil.findcmd(cmd, commands.table, False)
232 realcmd = aliases[0]
238 realcmd = aliases[0]
233 except (error.UnknownCommand, error.AmbiguousCommand,
239 except (error.UnknownCommand, error.AmbiguousCommand,
234 IndexError, getopt.GetoptError):
240 IndexError, getopt.GetoptError):
235 # Don't handle this here. We know the command is
241 # Don't handle this here. We know the command is
236 # invalid, but all we're worried about for now is that
242 # invalid, but all we're worried about for now is that
237 # it's not a command that server operators expect to
243 # it's not a command that server operators expect to
238 # be safe to offer to users in a sandbox.
244 # be safe to offer to users in a sandbox.
239 pass
245 pass
240 if realcmd == 'serve' and '--stdio' in cmdargs:
246 if realcmd == 'serve' and '--stdio' in cmdargs:
241 # We want to constrain 'hg serve --stdio' instances pretty
247 # We want to constrain 'hg serve --stdio' instances pretty
242 # closely, as many shared-ssh access tools want to grant
248 # closely, as many shared-ssh access tools want to grant
243 # access to run *only* 'hg -R $repo serve --stdio'. We
249 # access to run *only* 'hg -R $repo serve --stdio'. We
244 # restrict to exactly that set of arguments, and prohibit
250 # restrict to exactly that set of arguments, and prohibit
245 # any repo name that starts with '--' to prevent
251 # any repo name that starts with '--' to prevent
246 # shenanigans wherein a user does something like pass
252 # shenanigans wherein a user does something like pass
247 # --debugger or --config=ui.debugger=1 as a repo
253 # --debugger or --config=ui.debugger=1 as a repo
248 # name. This used to actually run the debugger.
254 # name. This used to actually run the debugger.
249 if (len(req.args) != 4 or
255 if (len(req.args) != 4 or
250 req.args[0] != '-R' or
256 req.args[0] != '-R' or
251 req.args[1].startswith('--') or
257 req.args[1].startswith('--') or
252 req.args[2] != 'serve' or
258 req.args[2] != 'serve' or
253 req.args[3] != '--stdio'):
259 req.args[3] != '--stdio'):
254 raise error.Abort(
260 raise error.Abort(
255 _('potentially unsafe serve --stdio invocation: %r') %
261 _('potentially unsafe serve --stdio invocation: %r') %
256 (req.args,))
262 (req.args,))
257
263
258 try:
264 try:
259 debugger = 'pdb'
265 debugger = 'pdb'
260 debugtrace = {
266 debugtrace = {
261 'pdb': pdb.set_trace
267 'pdb': pdb.set_trace
262 }
268 }
263 debugmortem = {
269 debugmortem = {
264 'pdb': pdb.post_mortem
270 'pdb': pdb.post_mortem
265 }
271 }
266
272
267 # read --config before doing anything else
273 # read --config before doing anything else
268 # (e.g. to change trust settings for reading .hg/hgrc)
274 # (e.g. to change trust settings for reading .hg/hgrc)
269 cfgs = _parseconfig(req.ui, req.earlyoptions['config'])
275 cfgs = _parseconfig(req.ui, req.earlyoptions['config'])
270
276
271 if req.repo:
277 if req.repo:
272 # copy configs that were passed on the cmdline (--config) to
278 # copy configs that were passed on the cmdline (--config) to
273 # the repo ui
279 # the repo ui
274 for sec, name, val in cfgs:
280 for sec, name, val in cfgs:
275 req.repo.ui.setconfig(sec, name, val, source='--config')
281 req.repo.ui.setconfig(sec, name, val, source='--config')
276
282
277 # developer config: ui.debugger
283 # developer config: ui.debugger
278 debugger = ui.config("ui", "debugger")
284 debugger = ui.config("ui", "debugger")
279 debugmod = pdb
285 debugmod = pdb
280 if not debugger or ui.plain():
286 if not debugger or ui.plain():
281 # if we are in HGPLAIN mode, then disable custom debugging
287 # if we are in HGPLAIN mode, then disable custom debugging
282 debugger = 'pdb'
288 debugger = 'pdb'
283 elif req.earlyoptions['debugger']:
289 elif req.earlyoptions['debugger']:
284 # This import can be slow for fancy debuggers, so only
290 # This import can be slow for fancy debuggers, so only
285 # do it when absolutely necessary, i.e. when actual
291 # do it when absolutely necessary, i.e. when actual
286 # debugging has been requested
292 # debugging has been requested
287 with demandimport.deactivated():
293 with demandimport.deactivated():
288 try:
294 try:
289 debugmod = __import__(debugger)
295 debugmod = __import__(debugger)
290 except ImportError:
296 except ImportError:
291 pass # Leave debugmod = pdb
297 pass # Leave debugmod = pdb
292
298
293 debugtrace[debugger] = debugmod.set_trace
299 debugtrace[debugger] = debugmod.set_trace
294 debugmortem[debugger] = debugmod.post_mortem
300 debugmortem[debugger] = debugmod.post_mortem
295
301
296 # enter the debugger before command execution
302 # enter the debugger before command execution
297 if req.earlyoptions['debugger']:
303 if req.earlyoptions['debugger']:
298 ui.warn(_("entering debugger - "
304 ui.warn(_("entering debugger - "
299 "type c to continue starting hg or h for help\n"))
305 "type c to continue starting hg or h for help\n"))
300
306
301 if (debugger != 'pdb' and
307 if (debugger != 'pdb' and
302 debugtrace[debugger] == debugtrace['pdb']):
308 debugtrace[debugger] == debugtrace['pdb']):
303 ui.warn(_("%s debugger specified "
309 ui.warn(_("%s debugger specified "
304 "but its module was not found\n") % debugger)
310 "but its module was not found\n") % debugger)
305 with demandimport.deactivated():
311 with demandimport.deactivated():
306 debugtrace[debugger]()
312 debugtrace[debugger]()
307 try:
313 try:
308 return _dispatch(req)
314 return _dispatch(req)
309 finally:
315 finally:
310 ui.flush()
316 ui.flush()
311 except: # re-raises
317 except: # re-raises
312 # enter the debugger when we hit an exception
318 # enter the debugger when we hit an exception
313 if req.earlyoptions['debugger']:
319 if req.earlyoptions['debugger']:
314 traceback.print_exc()
320 traceback.print_exc()
315 debugmortem[debugger](sys.exc_info()[2])
321 debugmortem[debugger](sys.exc_info()[2])
316 raise
322 raise
317
323
318 return _callcatch(ui, _runcatchfunc)
324 return _callcatch(ui, _runcatchfunc)
319
325
320 def _callcatch(ui, func):
326 def _callcatch(ui, func):
321 """like scmutil.callcatch but handles more high-level exceptions about
327 """like scmutil.callcatch but handles more high-level exceptions about
322 config parsing and commands. besides, use handlecommandexception to handle
328 config parsing and commands. besides, use handlecommandexception to handle
323 uncaught exceptions.
329 uncaught exceptions.
324 """
330 """
325 try:
331 try:
326 return scmutil.callcatch(ui, func)
332 return scmutil.callcatch(ui, func)
327 except error.AmbiguousCommand as inst:
333 except error.AmbiguousCommand as inst:
328 ui.warn(_("hg: command '%s' is ambiguous:\n %s\n") %
334 ui.warn(_("hg: command '%s' is ambiguous:\n %s\n") %
329 (inst.args[0], " ".join(inst.args[1])))
335 (inst.args[0], " ".join(inst.args[1])))
330 except error.CommandError as inst:
336 except error.CommandError as inst:
331 if inst.args[0]:
337 if inst.args[0]:
332 ui.pager('help')
338 ui.pager('help')
333 msgbytes = pycompat.bytestr(inst.args[1])
339 msgbytes = pycompat.bytestr(inst.args[1])
334 ui.warn(_("hg %s: %s\n") % (inst.args[0], msgbytes))
340 ui.warn(_("hg %s: %s\n") % (inst.args[0], msgbytes))
335 commands.help_(ui, inst.args[0], full=False, command=True)
341 commands.help_(ui, inst.args[0], full=False, command=True)
336 else:
342 else:
337 ui.pager('help')
343 ui.pager('help')
338 ui.warn(_("hg: %s\n") % inst.args[1])
344 ui.warn(_("hg: %s\n") % inst.args[1])
339 commands.help_(ui, 'shortlist')
345 commands.help_(ui, 'shortlist')
340 except error.ParseError as inst:
346 except error.ParseError as inst:
341 _formatparse(ui.warn, inst)
347 _formatparse(ui.warn, inst)
342 return -1
348 return -1
343 except error.UnknownCommand as inst:
349 except error.UnknownCommand as inst:
344 nocmdmsg = _("hg: unknown command '%s'\n") % inst.args[0]
350 nocmdmsg = _("hg: unknown command '%s'\n") % inst.args[0]
345 try:
351 try:
346 # check if the command is in a disabled extension
352 # check if the command is in a disabled extension
347 # (but don't check for extensions themselves)
353 # (but don't check for extensions themselves)
348 formatted = help.formattedhelp(ui, commands, inst.args[0],
354 formatted = help.formattedhelp(ui, commands, inst.args[0],
349 unknowncmd=True)
355 unknowncmd=True)
350 ui.warn(nocmdmsg)
356 ui.warn(nocmdmsg)
351 ui.write(formatted)
357 ui.write(formatted)
352 except (error.UnknownCommand, error.Abort):
358 except (error.UnknownCommand, error.Abort):
353 suggested = False
359 suggested = False
354 if len(inst.args) == 2:
360 if len(inst.args) == 2:
355 sim = _getsimilar(inst.args[1], inst.args[0])
361 sim = _getsimilar(inst.args[1], inst.args[0])
356 if sim:
362 if sim:
357 ui.warn(nocmdmsg)
363 ui.warn(nocmdmsg)
358 _reportsimilar(ui.warn, sim)
364 _reportsimilar(ui.warn, sim)
359 suggested = True
365 suggested = True
360 if not suggested:
366 if not suggested:
361 ui.pager('help')
367 ui.pager('help')
362 ui.warn(nocmdmsg)
368 ui.warn(nocmdmsg)
363 commands.help_(ui, 'shortlist')
369 commands.help_(ui, 'shortlist')
364 except IOError:
370 except IOError:
365 raise
371 raise
366 except KeyboardInterrupt:
372 except KeyboardInterrupt:
367 raise
373 raise
368 except: # probably re-raises
374 except: # probably re-raises
369 if not handlecommandexception(ui):
375 if not handlecommandexception(ui):
370 raise
376 raise
371
377
372 return -1
378 return -1
373
379
374 def aliasargs(fn, givenargs):
380 def aliasargs(fn, givenargs):
375 args = []
381 args = []
376 # only care about alias 'args', ignore 'args' set by extensions.wrapfunction
382 # only care about alias 'args', ignore 'args' set by extensions.wrapfunction
377 if not util.safehasattr(fn, '_origfunc'):
383 if not util.safehasattr(fn, '_origfunc'):
378 args = getattr(fn, 'args', args)
384 args = getattr(fn, 'args', args)
379 if args:
385 if args:
380 cmd = ' '.join(map(util.shellquote, args))
386 cmd = ' '.join(map(util.shellquote, args))
381
387
382 nums = []
388 nums = []
383 def replacer(m):
389 def replacer(m):
384 num = int(m.group(1)) - 1
390 num = int(m.group(1)) - 1
385 nums.append(num)
391 nums.append(num)
386 if num < len(givenargs):
392 if num < len(givenargs):
387 return givenargs[num]
393 return givenargs[num]
388 raise error.Abort(_('too few arguments for command alias'))
394 raise error.Abort(_('too few arguments for command alias'))
389 cmd = re.sub(br'\$(\d+|\$)', replacer, cmd)
395 cmd = re.sub(br'\$(\d+|\$)', replacer, cmd)
390 givenargs = [x for i, x in enumerate(givenargs)
396 givenargs = [x for i, x in enumerate(givenargs)
391 if i not in nums]
397 if i not in nums]
392 args = pycompat.shlexsplit(cmd)
398 args = pycompat.shlexsplit(cmd)
393 return args + givenargs
399 return args + givenargs
394
400
395 def aliasinterpolate(name, args, cmd):
401 def aliasinterpolate(name, args, cmd):
396 '''interpolate args into cmd for shell aliases
402 '''interpolate args into cmd for shell aliases
397
403
398 This also handles $0, $@ and "$@".
404 This also handles $0, $@ and "$@".
399 '''
405 '''
400 # util.interpolate can't deal with "$@" (with quotes) because it's only
406 # util.interpolate can't deal with "$@" (with quotes) because it's only
401 # built to match prefix + patterns.
407 # built to match prefix + patterns.
402 replacemap = dict(('$%d' % (i + 1), arg) for i, arg in enumerate(args))
408 replacemap = dict(('$%d' % (i + 1), arg) for i, arg in enumerate(args))
403 replacemap['$0'] = name
409 replacemap['$0'] = name
404 replacemap['$$'] = '$'
410 replacemap['$$'] = '$'
405 replacemap['$@'] = ' '.join(args)
411 replacemap['$@'] = ' '.join(args)
406 # Typical Unix shells interpolate "$@" (with quotes) as all the positional
412 # Typical Unix shells interpolate "$@" (with quotes) as all the positional
407 # parameters, separated out into words. Emulate the same behavior here by
413 # parameters, separated out into words. Emulate the same behavior here by
408 # quoting the arguments individually. POSIX shells will then typically
414 # quoting the arguments individually. POSIX shells will then typically
409 # tokenize each argument into exactly one word.
415 # tokenize each argument into exactly one word.
410 replacemap['"$@"'] = ' '.join(util.shellquote(arg) for arg in args)
416 replacemap['"$@"'] = ' '.join(util.shellquote(arg) for arg in args)
411 # escape '\$' for regex
417 # escape '\$' for regex
412 regex = '|'.join(replacemap.keys()).replace('$', br'\$')
418 regex = '|'.join(replacemap.keys()).replace('$', br'\$')
413 r = re.compile(regex)
419 r = re.compile(regex)
414 return r.sub(lambda x: replacemap[x.group()], cmd)
420 return r.sub(lambda x: replacemap[x.group()], cmd)
415
421
416 class cmdalias(object):
422 class cmdalias(object):
417 def __init__(self, name, definition, cmdtable, source):
423 def __init__(self, name, definition, cmdtable, source):
418 self.name = self.cmd = name
424 self.name = self.cmd = name
419 self.cmdname = ''
425 self.cmdname = ''
420 self.definition = definition
426 self.definition = definition
421 self.fn = None
427 self.fn = None
422 self.givenargs = []
428 self.givenargs = []
423 self.opts = []
429 self.opts = []
424 self.help = ''
430 self.help = ''
425 self.badalias = None
431 self.badalias = None
426 self.unknowncmd = False
432 self.unknowncmd = False
427 self.source = source
433 self.source = source
428
434
429 try:
435 try:
430 aliases, entry = cmdutil.findcmd(self.name, cmdtable)
436 aliases, entry = cmdutil.findcmd(self.name, cmdtable)
431 for alias, e in cmdtable.iteritems():
437 for alias, e in cmdtable.iteritems():
432 if e is entry:
438 if e is entry:
433 self.cmd = alias
439 self.cmd = alias
434 break
440 break
435 self.shadows = True
441 self.shadows = True
436 except error.UnknownCommand:
442 except error.UnknownCommand:
437 self.shadows = False
443 self.shadows = False
438
444
439 if not self.definition:
445 if not self.definition:
440 self.badalias = _("no definition for alias '%s'") % self.name
446 self.badalias = _("no definition for alias '%s'") % self.name
441 return
447 return
442
448
443 if self.definition.startswith('!'):
449 if self.definition.startswith('!'):
444 self.shell = True
450 self.shell = True
445 def fn(ui, *args):
451 def fn(ui, *args):
446 env = {'HG_ARGS': ' '.join((self.name,) + args)}
452 env = {'HG_ARGS': ' '.join((self.name,) + args)}
447 def _checkvar(m):
453 def _checkvar(m):
448 if m.groups()[0] == '$':
454 if m.groups()[0] == '$':
449 return m.group()
455 return m.group()
450 elif int(m.groups()[0]) <= len(args):
456 elif int(m.groups()[0]) <= len(args):
451 return m.group()
457 return m.group()
452 else:
458 else:
453 ui.debug("No argument found for substitution "
459 ui.debug("No argument found for substitution "
454 "of %i variable in alias '%s' definition.\n"
460 "of %i variable in alias '%s' definition.\n"
455 % (int(m.groups()[0]), self.name))
461 % (int(m.groups()[0]), self.name))
456 return ''
462 return ''
457 cmd = re.sub(br'\$(\d+|\$)', _checkvar, self.definition[1:])
463 cmd = re.sub(br'\$(\d+|\$)', _checkvar, self.definition[1:])
458 cmd = aliasinterpolate(self.name, args, cmd)
464 cmd = aliasinterpolate(self.name, args, cmd)
459 return ui.system(cmd, environ=env,
465 return ui.system(cmd, environ=env,
460 blockedtag='alias_%s' % self.name)
466 blockedtag='alias_%s' % self.name)
461 self.fn = fn
467 self.fn = fn
462 return
468 return
463
469
464 try:
470 try:
465 args = pycompat.shlexsplit(self.definition)
471 args = pycompat.shlexsplit(self.definition)
466 except ValueError as inst:
472 except ValueError as inst:
467 self.badalias = (_("error in definition for alias '%s': %s")
473 self.badalias = (_("error in definition for alias '%s': %s")
468 % (self.name, inst))
474 % (self.name, inst))
469 return
475 return
470 earlyopts, args = _earlysplitopts(args)
476 earlyopts, args = _earlysplitopts(args)
471 if earlyopts:
477 if earlyopts:
472 self.badalias = (_("error in definition for alias '%s': %s may "
478 self.badalias = (_("error in definition for alias '%s': %s may "
473 "only be given on the command line")
479 "only be given on the command line")
474 % (self.name, '/'.join(zip(*earlyopts)[0])))
480 % (self.name, '/'.join(zip(*earlyopts)[0])))
475 return
481 return
476 self.cmdname = cmd = args.pop(0)
482 self.cmdname = cmd = args.pop(0)
477 self.givenargs = args
483 self.givenargs = args
478
484
479 try:
485 try:
480 tableentry = cmdutil.findcmd(cmd, cmdtable, False)[1]
486 tableentry = cmdutil.findcmd(cmd, cmdtable, False)[1]
481 if len(tableentry) > 2:
487 if len(tableentry) > 2:
482 self.fn, self.opts, self.help = tableentry
488 self.fn, self.opts, self.help = tableentry
483 else:
489 else:
484 self.fn, self.opts = tableentry
490 self.fn, self.opts = tableentry
485
491
486 if self.help.startswith("hg " + cmd):
492 if self.help.startswith("hg " + cmd):
487 # drop prefix in old-style help lines so hg shows the alias
493 # drop prefix in old-style help lines so hg shows the alias
488 self.help = self.help[4 + len(cmd):]
494 self.help = self.help[4 + len(cmd):]
489 self.__doc__ = self.fn.__doc__
495 self.__doc__ = self.fn.__doc__
490
496
491 except error.UnknownCommand:
497 except error.UnknownCommand:
492 self.badalias = (_("alias '%s' resolves to unknown command '%s'")
498 self.badalias = (_("alias '%s' resolves to unknown command '%s'")
493 % (self.name, cmd))
499 % (self.name, cmd))
494 self.unknowncmd = True
500 self.unknowncmd = True
495 except error.AmbiguousCommand:
501 except error.AmbiguousCommand:
496 self.badalias = (_("alias '%s' resolves to ambiguous command '%s'")
502 self.badalias = (_("alias '%s' resolves to ambiguous command '%s'")
497 % (self.name, cmd))
503 % (self.name, cmd))
498
504
499 @property
505 @property
500 def args(self):
506 def args(self):
501 args = pycompat.maplist(util.expandpath, self.givenargs)
507 args = pycompat.maplist(util.expandpath, self.givenargs)
502 return aliasargs(self.fn, args)
508 return aliasargs(self.fn, args)
503
509
504 def __getattr__(self, name):
510 def __getattr__(self, name):
505 adefaults = {r'norepo': True, r'cmdtype': unrecoverablewrite,
511 adefaults = {r'norepo': True, r'cmdtype': unrecoverablewrite,
506 r'optionalrepo': False, r'inferrepo': False}
512 r'optionalrepo': False, r'inferrepo': False}
507 if name not in adefaults:
513 if name not in adefaults:
508 raise AttributeError(name)
514 raise AttributeError(name)
509 if self.badalias or util.safehasattr(self, 'shell'):
515 if self.badalias or util.safehasattr(self, 'shell'):
510 return adefaults[name]
516 return adefaults[name]
511 return getattr(self.fn, name)
517 return getattr(self.fn, name)
512
518
513 def __call__(self, ui, *args, **opts):
519 def __call__(self, ui, *args, **opts):
514 if self.badalias:
520 if self.badalias:
515 hint = None
521 hint = None
516 if self.unknowncmd:
522 if self.unknowncmd:
517 try:
523 try:
518 # check if the command is in a disabled extension
524 # check if the command is in a disabled extension
519 cmd, ext = extensions.disabledcmd(ui, self.cmdname)[:2]
525 cmd, ext = extensions.disabledcmd(ui, self.cmdname)[:2]
520 hint = _("'%s' is provided by '%s' extension") % (cmd, ext)
526 hint = _("'%s' is provided by '%s' extension") % (cmd, ext)
521 except error.UnknownCommand:
527 except error.UnknownCommand:
522 pass
528 pass
523 raise error.Abort(self.badalias, hint=hint)
529 raise error.Abort(self.badalias, hint=hint)
524 if self.shadows:
530 if self.shadows:
525 ui.debug("alias '%s' shadows command '%s'\n" %
531 ui.debug("alias '%s' shadows command '%s'\n" %
526 (self.name, self.cmdname))
532 (self.name, self.cmdname))
527
533
528 ui.log('commandalias', "alias '%s' expands to '%s'\n",
534 ui.log('commandalias', "alias '%s' expands to '%s'\n",
529 self.name, self.definition)
535 self.name, self.definition)
530 if util.safehasattr(self, 'shell'):
536 if util.safehasattr(self, 'shell'):
531 return self.fn(ui, *args, **opts)
537 return self.fn(ui, *args, **opts)
532 else:
538 else:
533 try:
539 try:
534 return util.checksignature(self.fn)(ui, *args, **opts)
540 return util.checksignature(self.fn)(ui, *args, **opts)
535 except error.SignatureError:
541 except error.SignatureError:
536 args = ' '.join([self.cmdname] + self.args)
542 args = ' '.join([self.cmdname] + self.args)
537 ui.debug("alias '%s' expands to '%s'\n" % (self.name, args))
543 ui.debug("alias '%s' expands to '%s'\n" % (self.name, args))
538 raise
544 raise
539
545
540 class lazyaliasentry(object):
546 class lazyaliasentry(object):
541 """like a typical command entry (func, opts, help), but is lazy"""
547 """like a typical command entry (func, opts, help), but is lazy"""
542
548
543 def __init__(self, name, definition, cmdtable, source):
549 def __init__(self, name, definition, cmdtable, source):
544 self.name = name
550 self.name = name
545 self.definition = definition
551 self.definition = definition
546 self.cmdtable = cmdtable.copy()
552 self.cmdtable = cmdtable.copy()
547 self.source = source
553 self.source = source
548
554
549 @util.propertycache
555 @util.propertycache
550 def _aliasdef(self):
556 def _aliasdef(self):
551 return cmdalias(self.name, self.definition, self.cmdtable, self.source)
557 return cmdalias(self.name, self.definition, self.cmdtable, self.source)
552
558
553 def __getitem__(self, n):
559 def __getitem__(self, n):
554 aliasdef = self._aliasdef
560 aliasdef = self._aliasdef
555 if n == 0:
561 if n == 0:
556 return aliasdef
562 return aliasdef
557 elif n == 1:
563 elif n == 1:
558 return aliasdef.opts
564 return aliasdef.opts
559 elif n == 2:
565 elif n == 2:
560 return aliasdef.help
566 return aliasdef.help
561 else:
567 else:
562 raise IndexError
568 raise IndexError
563
569
564 def __iter__(self):
570 def __iter__(self):
565 for i in range(3):
571 for i in range(3):
566 yield self[i]
572 yield self[i]
567
573
568 def __len__(self):
574 def __len__(self):
569 return 3
575 return 3
570
576
571 def addaliases(ui, cmdtable):
577 def addaliases(ui, cmdtable):
572 # aliases are processed after extensions have been loaded, so they
578 # aliases are processed after extensions have been loaded, so they
573 # may use extension commands. Aliases can also use other alias definitions,
579 # may use extension commands. Aliases can also use other alias definitions,
574 # but only if they have been defined prior to the current definition.
580 # but only if they have been defined prior to the current definition.
575 for alias, definition in ui.configitems('alias'):
581 for alias, definition in ui.configitems('alias'):
576 try:
582 try:
577 if cmdtable[alias].definition == definition:
583 if cmdtable[alias].definition == definition:
578 continue
584 continue
579 except (KeyError, AttributeError):
585 except (KeyError, AttributeError):
580 # definition might not exist or it might not be a cmdalias
586 # definition might not exist or it might not be a cmdalias
581 pass
587 pass
582
588
583 source = ui.configsource('alias', alias)
589 source = ui.configsource('alias', alias)
584 entry = lazyaliasentry(alias, definition, cmdtable, source)
590 entry = lazyaliasentry(alias, definition, cmdtable, source)
585 cmdtable[alias] = entry
591 cmdtable[alias] = entry
586
592
587 def _parse(ui, args):
593 def _parse(ui, args):
588 options = {}
594 options = {}
589 cmdoptions = {}
595 cmdoptions = {}
590
596
591 try:
597 try:
592 args = fancyopts.fancyopts(args, commands.globalopts, options)
598 args = fancyopts.fancyopts(args, commands.globalopts, options)
593 except getopt.GetoptError as inst:
599 except getopt.GetoptError as inst:
594 raise error.CommandError(None, inst)
600 raise error.CommandError(None, inst)
595
601
596 if args:
602 if args:
597 cmd, args = args[0], args[1:]
603 cmd, args = args[0], args[1:]
598 aliases, entry = cmdutil.findcmd(cmd, commands.table,
604 aliases, entry = cmdutil.findcmd(cmd, commands.table,
599 ui.configbool("ui", "strict"))
605 ui.configbool("ui", "strict"))
600 cmd = aliases[0]
606 cmd = aliases[0]
601 args = aliasargs(entry[0], args)
607 args = aliasargs(entry[0], args)
602 defaults = ui.config("defaults", cmd)
608 defaults = ui.config("defaults", cmd)
603 if defaults:
609 if defaults:
604 args = pycompat.maplist(
610 args = pycompat.maplist(
605 util.expandpath, pycompat.shlexsplit(defaults)) + args
611 util.expandpath, pycompat.shlexsplit(defaults)) + args
606 c = list(entry[1])
612 c = list(entry[1])
607 else:
613 else:
608 cmd = None
614 cmd = None
609 c = []
615 c = []
610
616
611 # combine global options into local
617 # combine global options into local
612 for o in commands.globalopts:
618 for o in commands.globalopts:
613 c.append((o[0], o[1], options[o[1]], o[3]))
619 c.append((o[0], o[1], options[o[1]], o[3]))
614
620
615 try:
621 try:
616 args = fancyopts.fancyopts(args, c, cmdoptions, gnu=True)
622 args = fancyopts.fancyopts(args, c, cmdoptions, gnu=True)
617 except getopt.GetoptError as inst:
623 except getopt.GetoptError as inst:
618 raise error.CommandError(cmd, inst)
624 raise error.CommandError(cmd, inst)
619
625
620 # separate global options back out
626 # separate global options back out
621 for o in commands.globalopts:
627 for o in commands.globalopts:
622 n = o[1]
628 n = o[1]
623 options[n] = cmdoptions[n]
629 options[n] = cmdoptions[n]
624 del cmdoptions[n]
630 del cmdoptions[n]
625
631
626 return (cmd, cmd and entry[0] or None, args, options, cmdoptions)
632 return (cmd, cmd and entry[0] or None, args, options, cmdoptions)
627
633
628 def _parseconfig(ui, config):
634 def _parseconfig(ui, config):
629 """parse the --config options from the command line"""
635 """parse the --config options from the command line"""
630 configs = []
636 configs = []
631
637
632 for cfg in config:
638 for cfg in config:
633 try:
639 try:
634 name, value = [cfgelem.strip()
640 name, value = [cfgelem.strip()
635 for cfgelem in cfg.split('=', 1)]
641 for cfgelem in cfg.split('=', 1)]
636 section, name = name.split('.', 1)
642 section, name = name.split('.', 1)
637 if not section or not name:
643 if not section or not name:
638 raise IndexError
644 raise IndexError
639 ui.setconfig(section, name, value, '--config')
645 ui.setconfig(section, name, value, '--config')
640 configs.append((section, name, value))
646 configs.append((section, name, value))
641 except (IndexError, ValueError):
647 except (IndexError, ValueError):
642 raise error.Abort(_('malformed --config option: %r '
648 raise error.Abort(_('malformed --config option: %r '
643 '(use --config section.name=value)') % cfg)
649 '(use --config section.name=value)') % cfg)
644
650
645 return configs
651 return configs
646
652
647 def _earlyparseopts(ui, args):
653 def _earlyparseopts(ui, args):
648 options = {}
654 options = {}
649 fancyopts.fancyopts(args, commands.globalopts, options,
655 fancyopts.fancyopts(args, commands.globalopts, options,
650 gnu=not ui.plain('strictflags'), early=True,
656 gnu=not ui.plain('strictflags'), early=True,
651 optaliases={'repository': ['repo']})
657 optaliases={'repository': ['repo']})
652 return options
658 return options
653
659
654 def _earlysplitopts(args):
660 def _earlysplitopts(args):
655 """Split args into a list of possible early options and remainder args"""
661 """Split args into a list of possible early options and remainder args"""
656 shortoptions = 'R:'
662 shortoptions = 'R:'
657 # TODO: perhaps 'debugger' should be included
663 # TODO: perhaps 'debugger' should be included
658 longoptions = ['cwd=', 'repository=', 'repo=', 'config=']
664 longoptions = ['cwd=', 'repository=', 'repo=', 'config=']
659 return fancyopts.earlygetopt(args, shortoptions, longoptions,
665 return fancyopts.earlygetopt(args, shortoptions, longoptions,
660 gnu=True, keepsep=True)
666 gnu=True, keepsep=True)
661
667
662 def runcommand(lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions):
668 def runcommand(lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions):
663 # run pre-hook, and abort if it fails
669 # run pre-hook, and abort if it fails
664 hook.hook(lui, repo, "pre-%s" % cmd, True, args=" ".join(fullargs),
670 hook.hook(lui, repo, "pre-%s" % cmd, True, args=" ".join(fullargs),
665 pats=cmdpats, opts=cmdoptions)
671 pats=cmdpats, opts=cmdoptions)
666 try:
672 try:
667 ret = _runcommand(ui, options, cmd, d)
673 ret = _runcommand(ui, options, cmd, d)
668 # run post-hook, passing command result
674 # run post-hook, passing command result
669 hook.hook(lui, repo, "post-%s" % cmd, False, args=" ".join(fullargs),
675 hook.hook(lui, repo, "post-%s" % cmd, False, args=" ".join(fullargs),
670 result=ret, pats=cmdpats, opts=cmdoptions)
676 result=ret, pats=cmdpats, opts=cmdoptions)
671 except Exception:
677 except Exception:
672 # run failure hook and re-raise
678 # run failure hook and re-raise
673 hook.hook(lui, repo, "fail-%s" % cmd, False, args=" ".join(fullargs),
679 hook.hook(lui, repo, "fail-%s" % cmd, False, args=" ".join(fullargs),
674 pats=cmdpats, opts=cmdoptions)
680 pats=cmdpats, opts=cmdoptions)
675 raise
681 raise
676 return ret
682 return ret
677
683
678 def _getlocal(ui, rpath, wd=None):
684 def _getlocal(ui, rpath, wd=None):
679 """Return (path, local ui object) for the given target path.
685 """Return (path, local ui object) for the given target path.
680
686
681 Takes paths in [cwd]/.hg/hgrc into account."
687 Takes paths in [cwd]/.hg/hgrc into account."
682 """
688 """
683 if wd is None:
689 if wd is None:
684 try:
690 try:
685 wd = pycompat.getcwd()
691 wd = pycompat.getcwd()
686 except OSError as e:
692 except OSError as e:
687 raise error.Abort(_("error getting current working directory: %s") %
693 raise error.Abort(_("error getting current working directory: %s") %
688 encoding.strtolocal(e.strerror))
694 encoding.strtolocal(e.strerror))
689 path = cmdutil.findrepo(wd) or ""
695 path = cmdutil.findrepo(wd) or ""
690 if not path:
696 if not path:
691 lui = ui
697 lui = ui
692 else:
698 else:
693 lui = ui.copy()
699 lui = ui.copy()
694 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
700 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
695
701
696 if rpath:
702 if rpath:
697 path = lui.expandpath(rpath)
703 path = lui.expandpath(rpath)
698 lui = ui.copy()
704 lui = ui.copy()
699 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
705 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
700
706
701 return path, lui
707 return path, lui
702
708
703 def _checkshellalias(lui, ui, args):
709 def _checkshellalias(lui, ui, args):
704 """Return the function to run the shell alias, if it is required"""
710 """Return the function to run the shell alias, if it is required"""
705 options = {}
711 options = {}
706
712
707 try:
713 try:
708 args = fancyopts.fancyopts(args, commands.globalopts, options)
714 args = fancyopts.fancyopts(args, commands.globalopts, options)
709 except getopt.GetoptError:
715 except getopt.GetoptError:
710 return
716 return
711
717
712 if not args:
718 if not args:
713 return
719 return
714
720
715 cmdtable = commands.table
721 cmdtable = commands.table
716
722
717 cmd = args[0]
723 cmd = args[0]
718 try:
724 try:
719 strict = ui.configbool("ui", "strict")
725 strict = ui.configbool("ui", "strict")
720 aliases, entry = cmdutil.findcmd(cmd, cmdtable, strict)
726 aliases, entry = cmdutil.findcmd(cmd, cmdtable, strict)
721 except (error.AmbiguousCommand, error.UnknownCommand):
727 except (error.AmbiguousCommand, error.UnknownCommand):
722 return
728 return
723
729
724 cmd = aliases[0]
730 cmd = aliases[0]
725 fn = entry[0]
731 fn = entry[0]
726
732
727 if cmd and util.safehasattr(fn, 'shell'):
733 if cmd and util.safehasattr(fn, 'shell'):
728 # shell alias shouldn't receive early options which are consumed by hg
734 # shell alias shouldn't receive early options which are consumed by hg
729 _earlyopts, args = _earlysplitopts(args)
735 _earlyopts, args = _earlysplitopts(args)
730 d = lambda: fn(ui, *args[1:])
736 d = lambda: fn(ui, *args[1:])
731 return lambda: runcommand(lui, None, cmd, args[:1], ui, options, d,
737 return lambda: runcommand(lui, None, cmd, args[:1], ui, options, d,
732 [], {})
738 [], {})
733
739
734 def _dispatch(req):
740 def _dispatch(req):
735 args = req.args
741 args = req.args
736 ui = req.ui
742 ui = req.ui
737
743
738 # check for cwd
744 # check for cwd
739 cwd = req.earlyoptions['cwd']
745 cwd = req.earlyoptions['cwd']
740 if cwd:
746 if cwd:
741 os.chdir(cwd)
747 os.chdir(cwd)
742
748
743 rpath = req.earlyoptions['repository']
749 rpath = req.earlyoptions['repository']
744 path, lui = _getlocal(ui, rpath)
750 path, lui = _getlocal(ui, rpath)
745
751
746 uis = {ui, lui}
752 uis = {ui, lui}
747
753
748 if req.repo:
754 if req.repo:
749 uis.add(req.repo.ui)
755 uis.add(req.repo.ui)
750
756
751 if req.earlyoptions['profile']:
757 if req.earlyoptions['profile']:
752 for ui_ in uis:
758 for ui_ in uis:
753 ui_.setconfig('profiling', 'enabled', 'true', '--profile')
759 ui_.setconfig('profiling', 'enabled', 'true', '--profile')
754
760
755 profile = lui.configbool('profiling', 'enabled')
761 profile = lui.configbool('profiling', 'enabled')
756 with profiling.profile(lui, enabled=profile) as profiler:
762 with profiling.profile(lui, enabled=profile) as profiler:
757 # Configure extensions in phases: uisetup, extsetup, cmdtable, and
763 # Configure extensions in phases: uisetup, extsetup, cmdtable, and
758 # reposetup
764 # reposetup
759 extensions.loadall(lui)
765 extensions.loadall(lui)
760 # Propagate any changes to lui.__class__ by extensions
766 # Propagate any changes to lui.__class__ by extensions
761 ui.__class__ = lui.__class__
767 ui.__class__ = lui.__class__
762
768
763 # (uisetup and extsetup are handled in extensions.loadall)
769 # (uisetup and extsetup are handled in extensions.loadall)
764
770
765 # (reposetup is handled in hg.repository)
771 # (reposetup is handled in hg.repository)
766
772
767 addaliases(lui, commands.table)
773 addaliases(lui, commands.table)
768
774
769 # All aliases and commands are completely defined, now.
775 # All aliases and commands are completely defined, now.
770 # Check abbreviation/ambiguity of shell alias.
776 # Check abbreviation/ambiguity of shell alias.
771 shellaliasfn = _checkshellalias(lui, ui, args)
777 shellaliasfn = _checkshellalias(lui, ui, args)
772 if shellaliasfn:
778 if shellaliasfn:
773 return shellaliasfn()
779 return shellaliasfn()
774
780
775 # check for fallback encoding
781 # check for fallback encoding
776 fallback = lui.config('ui', 'fallbackencoding')
782 fallback = lui.config('ui', 'fallbackencoding')
777 if fallback:
783 if fallback:
778 encoding.fallbackencoding = fallback
784 encoding.fallbackencoding = fallback
779
785
780 fullargs = args
786 fullargs = args
781 cmd, func, args, options, cmdoptions = _parse(lui, args)
787 cmd, func, args, options, cmdoptions = _parse(lui, args)
782
788
783 if options["config"] != req.earlyoptions["config"]:
789 if options["config"] != req.earlyoptions["config"]:
784 raise error.Abort(_("option --config may not be abbreviated!"))
790 raise error.Abort(_("option --config may not be abbreviated!"))
785 if options["cwd"] != req.earlyoptions["cwd"]:
791 if options["cwd"] != req.earlyoptions["cwd"]:
786 raise error.Abort(_("option --cwd may not be abbreviated!"))
792 raise error.Abort(_("option --cwd may not be abbreviated!"))
787 if options["repository"] != req.earlyoptions["repository"]:
793 if options["repository"] != req.earlyoptions["repository"]:
788 raise error.Abort(_(
794 raise error.Abort(_(
789 "option -R has to be separated from other options (e.g. not "
795 "option -R has to be separated from other options (e.g. not "
790 "-qR) and --repository may only be abbreviated as --repo!"))
796 "-qR) and --repository may only be abbreviated as --repo!"))
791 if options["debugger"] != req.earlyoptions["debugger"]:
797 if options["debugger"] != req.earlyoptions["debugger"]:
792 raise error.Abort(_("option --debugger may not be abbreviated!"))
798 raise error.Abort(_("option --debugger may not be abbreviated!"))
793 # don't validate --profile/--traceback, which can be enabled from now
799 # don't validate --profile/--traceback, which can be enabled from now
794
800
795 if options["encoding"]:
801 if options["encoding"]:
796 encoding.encoding = options["encoding"]
802 encoding.encoding = options["encoding"]
797 if options["encodingmode"]:
803 if options["encodingmode"]:
798 encoding.encodingmode = options["encodingmode"]
804 encoding.encodingmode = options["encodingmode"]
799 if options["time"]:
805 if options["time"]:
800 def get_times():
806 def get_times():
801 t = os.times()
807 t = os.times()
802 if t[4] == 0.0:
808 if t[4] == 0.0:
803 # Windows leaves this as zero, so use time.clock()
809 # Windows leaves this as zero, so use time.clock()
804 t = (t[0], t[1], t[2], t[3], time.clock())
810 t = (t[0], t[1], t[2], t[3], time.clock())
805 return t
811 return t
806 s = get_times()
812 s = get_times()
807 def print_time():
813 def print_time():
808 t = get_times()
814 t = get_times()
809 ui.warn(
815 ui.warn(
810 _("time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
816 _("time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
811 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
817 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
812 ui.atexit(print_time)
818 ui.atexit(print_time)
813 if options["profile"]:
819 if options["profile"]:
814 profiler.start()
820 profiler.start()
815
821
816 if options['verbose'] or options['debug'] or options['quiet']:
822 if options['verbose'] or options['debug'] or options['quiet']:
817 for opt in ('verbose', 'debug', 'quiet'):
823 for opt in ('verbose', 'debug', 'quiet'):
818 val = str(bool(options[opt]))
824 val = str(bool(options[opt]))
819 if pycompat.ispy3:
825 if pycompat.ispy3:
820 val = val.encode('ascii')
826 val = val.encode('ascii')
821 for ui_ in uis:
827 for ui_ in uis:
822 ui_.setconfig('ui', opt, val, '--' + opt)
828 ui_.setconfig('ui', opt, val, '--' + opt)
823
829
824 if options['traceback']:
830 if options['traceback']:
825 for ui_ in uis:
831 for ui_ in uis:
826 ui_.setconfig('ui', 'traceback', 'on', '--traceback')
832 ui_.setconfig('ui', 'traceback', 'on', '--traceback')
827
833
828 if options['noninteractive']:
834 if options['noninteractive']:
829 for ui_ in uis:
835 for ui_ in uis:
830 ui_.setconfig('ui', 'interactive', 'off', '-y')
836 ui_.setconfig('ui', 'interactive', 'off', '-y')
831
837
832 if cmdoptions.get('insecure', False):
838 if cmdoptions.get('insecure', False):
833 for ui_ in uis:
839 for ui_ in uis:
834 ui_.insecureconnections = True
840 ui_.insecureconnections = True
835
841
836 # setup color handling before pager, because setting up pager
842 # setup color handling before pager, because setting up pager
837 # might cause incorrect console information
843 # might cause incorrect console information
838 coloropt = options['color']
844 coloropt = options['color']
839 for ui_ in uis:
845 for ui_ in uis:
840 if coloropt:
846 if coloropt:
841 ui_.setconfig('ui', 'color', coloropt, '--color')
847 ui_.setconfig('ui', 'color', coloropt, '--color')
842 color.setup(ui_)
848 color.setup(ui_)
843
849
844 if util.parsebool(options['pager']):
850 if util.parsebool(options['pager']):
845 # ui.pager() expects 'internal-always-' prefix in this case
851 # ui.pager() expects 'internal-always-' prefix in this case
846 ui.pager('internal-always-' + cmd)
852 ui.pager('internal-always-' + cmd)
847 elif options['pager'] != 'auto':
853 elif options['pager'] != 'auto':
848 for ui_ in uis:
854 for ui_ in uis:
849 ui_.disablepager()
855 ui_.disablepager()
850
856
851 if options['version']:
857 if options['version']:
852 return commands.version_(ui)
858 return commands.version_(ui)
853 if options['help']:
859 if options['help']:
854 return commands.help_(ui, cmd, command=cmd is not None)
860 return commands.help_(ui, cmd, command=cmd is not None)
855 elif not cmd:
861 elif not cmd:
856 return commands.help_(ui, 'shortlist')
862 return commands.help_(ui, 'shortlist')
857
863
858 repo = None
864 repo = None
859 cmdpats = args[:]
865 cmdpats = args[:]
860 if not func.norepo:
866 if not func.norepo:
861 # use the repo from the request only if we don't have -R
867 # use the repo from the request only if we don't have -R
862 if not rpath and not cwd:
868 if not rpath and not cwd:
863 repo = req.repo
869 repo = req.repo
864
870
865 if repo:
871 if repo:
866 # set the descriptors of the repo ui to those of ui
872 # set the descriptors of the repo ui to those of ui
867 repo.ui.fin = ui.fin
873 repo.ui.fin = ui.fin
868 repo.ui.fout = ui.fout
874 repo.ui.fout = ui.fout
869 repo.ui.ferr = ui.ferr
875 repo.ui.ferr = ui.ferr
870 else:
876 else:
871 try:
877 try:
872 repo = hg.repository(ui, path=path,
878 repo = hg.repository(ui, path=path,
873 presetupfuncs=req.prereposetups)
879 presetupfuncs=req.prereposetups)
874 if not repo.local():
880 if not repo.local():
875 raise error.Abort(_("repository '%s' is not local")
881 raise error.Abort(_("repository '%s' is not local")
876 % path)
882 % path)
877 repo.ui.setconfig("bundle", "mainreporoot", repo.root,
883 repo.ui.setconfig("bundle", "mainreporoot", repo.root,
878 'repo')
884 'repo')
879 except error.RequirementError:
885 except error.RequirementError:
880 raise
886 raise
881 except error.RepoError:
887 except error.RepoError:
882 if rpath: # invalid -R path
888 if rpath: # invalid -R path
883 raise
889 raise
884 if not func.optionalrepo:
890 if not func.optionalrepo:
885 if func.inferrepo and args and not path:
891 if func.inferrepo and args and not path:
886 # try to infer -R from command args
892 # try to infer -R from command args
887 repos = pycompat.maplist(cmdutil.findrepo, args)
893 repos = pycompat.maplist(cmdutil.findrepo, args)
888 guess = repos[0]
894 guess = repos[0]
889 if guess and repos.count(guess) == len(repos):
895 if guess and repos.count(guess) == len(repos):
890 req.args = ['--repository', guess] + fullargs
896 req.args = ['--repository', guess] + fullargs
891 req.earlyoptions['repository'] = guess
897 req.earlyoptions['repository'] = guess
892 return _dispatch(req)
898 return _dispatch(req)
893 if not path:
899 if not path:
894 raise error.RepoError(_("no repository found in"
900 raise error.RepoError(_("no repository found in"
895 " '%s' (.hg not found)")
901 " '%s' (.hg not found)")
896 % pycompat.getcwd())
902 % pycompat.getcwd())
897 raise
903 raise
898 if repo:
904 if repo:
899 ui = repo.ui
905 ui = repo.ui
900 if options['hidden']:
906 if options['hidden']:
901 repo = repo.unfiltered()
907 repo = repo.unfiltered()
902 args.insert(0, repo)
908 args.insert(0, repo)
903 elif rpath:
909 elif rpath:
904 ui.warn(_("warning: --repository ignored\n"))
910 ui.warn(_("warning: --repository ignored\n"))
905
911
906 msg = _formatargs(fullargs)
912 msg = _formatargs(fullargs)
907 ui.log("command", '%s\n', msg)
913 ui.log("command", '%s\n', msg)
908 strcmdopt = pycompat.strkwargs(cmdoptions)
914 strcmdopt = pycompat.strkwargs(cmdoptions)
909 d = lambda: util.checksignature(func)(ui, *args, **strcmdopt)
915 d = lambda: util.checksignature(func)(ui, *args, **strcmdopt)
910 try:
916 try:
911 return runcommand(lui, repo, cmd, fullargs, ui, options, d,
917 return runcommand(lui, repo, cmd, fullargs, ui, options, d,
912 cmdpats, cmdoptions)
918 cmdpats, cmdoptions)
913 finally:
919 finally:
914 if repo and repo != req.repo:
920 if repo and repo != req.repo:
915 repo.close()
921 repo.close()
916
922
917 def _runcommand(ui, options, cmd, cmdfunc):
923 def _runcommand(ui, options, cmd, cmdfunc):
918 """Run a command function, possibly with profiling enabled."""
924 """Run a command function, possibly with profiling enabled."""
919 try:
925 try:
920 return cmdfunc()
926 return cmdfunc()
921 except error.SignatureError:
927 except error.SignatureError:
922 raise error.CommandError(cmd, _('invalid arguments'))
928 raise error.CommandError(cmd, _('invalid arguments'))
923
929
924 def _exceptionwarning(ui):
930 def _exceptionwarning(ui):
925 """Produce a warning message for the current active exception"""
931 """Produce a warning message for the current active exception"""
926
932
927 # For compatibility checking, we discard the portion of the hg
933 # For compatibility checking, we discard the portion of the hg
928 # version after the + on the assumption that if a "normal
934 # version after the + on the assumption that if a "normal
929 # user" is running a build with a + in it the packager
935 # user" is running a build with a + in it the packager
930 # probably built from fairly close to a tag and anyone with a
936 # probably built from fairly close to a tag and anyone with a
931 # 'make local' copy of hg (where the version number can be out
937 # 'make local' copy of hg (where the version number can be out
932 # of date) will be clueful enough to notice the implausible
938 # of date) will be clueful enough to notice the implausible
933 # version number and try updating.
939 # version number and try updating.
934 ct = util.versiontuple(n=2)
940 ct = util.versiontuple(n=2)
935 worst = None, ct, ''
941 worst = None, ct, ''
936 if ui.config('ui', 'supportcontact') is None:
942 if ui.config('ui', 'supportcontact') is None:
937 for name, mod in extensions.extensions():
943 for name, mod in extensions.extensions():
938 testedwith = getattr(mod, 'testedwith', '')
944 testedwith = getattr(mod, 'testedwith', '')
939 if pycompat.ispy3 and isinstance(testedwith, str):
945 if pycompat.ispy3 and isinstance(testedwith, str):
940 testedwith = testedwith.encode(u'utf-8')
946 testedwith = testedwith.encode(u'utf-8')
941 report = getattr(mod, 'buglink', _('the extension author.'))
947 report = getattr(mod, 'buglink', _('the extension author.'))
942 if not testedwith.strip():
948 if not testedwith.strip():
943 # We found an untested extension. It's likely the culprit.
949 # We found an untested extension. It's likely the culprit.
944 worst = name, 'unknown', report
950 worst = name, 'unknown', report
945 break
951 break
946
952
947 # Never blame on extensions bundled with Mercurial.
953 # Never blame on extensions bundled with Mercurial.
948 if extensions.ismoduleinternal(mod):
954 if extensions.ismoduleinternal(mod):
949 continue
955 continue
950
956
951 tested = [util.versiontuple(t, 2) for t in testedwith.split()]
957 tested = [util.versiontuple(t, 2) for t in testedwith.split()]
952 if ct in tested:
958 if ct in tested:
953 continue
959 continue
954
960
955 lower = [t for t in tested if t < ct]
961 lower = [t for t in tested if t < ct]
956 nearest = max(lower or tested)
962 nearest = max(lower or tested)
957 if worst[0] is None or nearest < worst[1]:
963 if worst[0] is None or nearest < worst[1]:
958 worst = name, nearest, report
964 worst = name, nearest, report
959 if worst[0] is not None:
965 if worst[0] is not None:
960 name, testedwith, report = worst
966 name, testedwith, report = worst
961 if not isinstance(testedwith, (bytes, str)):
967 if not isinstance(testedwith, (bytes, str)):
962 testedwith = '.'.join([str(c) for c in testedwith])
968 testedwith = '.'.join([str(c) for c in testedwith])
963 warning = (_('** Unknown exception encountered with '
969 warning = (_('** Unknown exception encountered with '
964 'possibly-broken third-party extension %s\n'
970 'possibly-broken third-party extension %s\n'
965 '** which supports versions %s of Mercurial.\n'
971 '** which supports versions %s of Mercurial.\n'
966 '** Please disable %s and try your action again.\n'
972 '** Please disable %s and try your action again.\n'
967 '** If that fixes the bug please report it to %s\n')
973 '** If that fixes the bug please report it to %s\n')
968 % (name, testedwith, name, report))
974 % (name, testedwith, name, report))
969 else:
975 else:
970 bugtracker = ui.config('ui', 'supportcontact')
976 bugtracker = ui.config('ui', 'supportcontact')
971 if bugtracker is None:
977 if bugtracker is None:
972 bugtracker = _("https://mercurial-scm.org/wiki/BugTracker")
978 bugtracker = _("https://mercurial-scm.org/wiki/BugTracker")
973 warning = (_("** unknown exception encountered, "
979 warning = (_("** unknown exception encountered, "
974 "please report by visiting\n** ") + bugtracker + '\n')
980 "please report by visiting\n** ") + bugtracker + '\n')
975 if pycompat.ispy3:
981 if pycompat.ispy3:
976 sysversion = sys.version.encode(u'utf-8')
982 sysversion = sys.version.encode(u'utf-8')
977 else:
983 else:
978 sysversion = sys.version
984 sysversion = sys.version
979 sysversion = sysversion.replace('\n', '')
985 sysversion = sysversion.replace('\n', '')
980 warning += ((_("** Python %s\n") % sysversion) +
986 warning += ((_("** Python %s\n") % sysversion) +
981 (_("** Mercurial Distributed SCM (version %s)\n") %
987 (_("** Mercurial Distributed SCM (version %s)\n") %
982 util.version()) +
988 util.version()) +
983 (_("** Extensions loaded: %s\n") %
989 (_("** Extensions loaded: %s\n") %
984 ", ".join([x[0] for x in extensions.extensions()])))
990 ", ".join([x[0] for x in extensions.extensions()])))
985 return warning
991 return warning
986
992
987 def handlecommandexception(ui):
993 def handlecommandexception(ui):
988 """Produce a warning message for broken commands
994 """Produce a warning message for broken commands
989
995
990 Called when handling an exception; the exception is reraised if
996 Called when handling an exception; the exception is reraised if
991 this function returns False, ignored otherwise.
997 this function returns False, ignored otherwise.
992 """
998 """
993 warning = _exceptionwarning(ui)
999 warning = _exceptionwarning(ui)
994 ui.log("commandexception", "%s\n%s\n", warning, traceback.format_exc())
1000 ui.log("commandexception", "%s\n%s\n", warning, traceback.format_exc())
995 ui.warn(warning)
1001 ui.warn(warning)
996 return False # re-raise the exception
1002 return False # re-raise the exception
@@ -1,100 +1,92 b''
1 Create a repository:
1 Create a repository:
2
2
3 $ hg config
3 $ hg config
4 devel.all-warnings=true
4 devel.all-warnings=true
5 devel.default-date=0 0
5 devel.default-date=0 0
6 extensions.fsmonitor= (fsmonitor !)
6 extensions.fsmonitor= (fsmonitor !)
7 largefiles.usercache=$TESTTMP/.cache/largefiles
7 largefiles.usercache=$TESTTMP/.cache/largefiles
8 lfs.usercache=$TESTTMP/.cache/lfs
8 lfs.usercache=$TESTTMP/.cache/lfs
9 ui.slash=True
9 ui.slash=True
10 ui.interactive=False
10 ui.interactive=False
11 ui.mergemarkers=detailed
11 ui.mergemarkers=detailed
12 ui.promptecho=True
12 ui.promptecho=True
13 web.address=localhost
13 web.address=localhost
14 web\.ipv6=(?:True|False) (re)
14 web\.ipv6=(?:True|False) (re)
15 $ hg init t
15 $ hg init t
16 $ cd t
16 $ cd t
17
17
18 Prepare a changeset:
18 Prepare a changeset:
19
19
20 $ echo a > a
20 $ echo a > a
21 $ hg add a
21 $ hg add a
22
22
23 $ hg status
23 $ hg status
24 A a
24 A a
25
25
26 Writes to stdio succeed and fail appropriately
26 Writes to stdio succeed and fail appropriately
27
27
28 #if devfull
28 #if devfull
29 $ hg status 2>/dev/full
29 $ hg status 2>/dev/full
30 A a
30 A a
31
31
32 $ hg status >/dev/full
32 $ hg status >/dev/full
33 abort: No space left on device
33 abort: No space left on device
34 [255]
34 [255]
35 #endif
35 #endif
36
36
37 #if devfull no-chg
37 #if devfull
38 $ hg status >/dev/full 2>&1
39 [1]
40
41 $ hg status ENOENT 2>/dev/full
42 [1]
43 #endif
44
45 #if devfull chg
46 $ hg status >/dev/full 2>&1
38 $ hg status >/dev/full 2>&1
47 [255]
39 [255]
48
40
49 $ hg status ENOENT 2>/dev/full
41 $ hg status ENOENT 2>/dev/full
50 [255]
42 [255]
51 #endif
43 #endif
52
44
53 $ hg commit -m test
45 $ hg commit -m test
54
46
55 This command is ancient:
47 This command is ancient:
56
48
57 $ hg history
49 $ hg history
58 changeset: 0:acb14030fe0a
50 changeset: 0:acb14030fe0a
59 tag: tip
51 tag: tip
60 user: test
52 user: test
61 date: Thu Jan 01 00:00:00 1970 +0000
53 date: Thu Jan 01 00:00:00 1970 +0000
62 summary: test
54 summary: test
63
55
64
56
65 Verify that updating to revision 0 via commands.update() works properly
57 Verify that updating to revision 0 via commands.update() works properly
66
58
67 $ cat <<EOF > update_to_rev0.py
59 $ cat <<EOF > update_to_rev0.py
68 > from mercurial import ui, hg, commands
60 > from mercurial import ui, hg, commands
69 > myui = ui.ui.load()
61 > myui = ui.ui.load()
70 > repo = hg.repository(myui, path='.')
62 > repo = hg.repository(myui, path='.')
71 > commands.update(myui, repo, rev=0)
63 > commands.update(myui, repo, rev=0)
72 > EOF
64 > EOF
73 $ hg up null
65 $ hg up null
74 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
66 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
75 $ $PYTHON ./update_to_rev0.py
67 $ $PYTHON ./update_to_rev0.py
76 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
68 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
77 $ hg identify -n
69 $ hg identify -n
78 0
70 0
79
71
80
72
81 Poke around at hashes:
73 Poke around at hashes:
82
74
83 $ hg manifest --debug
75 $ hg manifest --debug
84 b789fdd96dc2f3bd229c1dd8eedf0fc60e2b68e3 644 a
76 b789fdd96dc2f3bd229c1dd8eedf0fc60e2b68e3 644 a
85
77
86 $ hg cat a
78 $ hg cat a
87 a
79 a
88
80
89 Verify should succeed:
81 Verify should succeed:
90
82
91 $ hg verify
83 $ hg verify
92 checking changesets
84 checking changesets
93 checking manifests
85 checking manifests
94 crosschecking files in changesets and manifests
86 crosschecking files in changesets and manifests
95 checking files
87 checking files
96 1 files, 1 changesets, 1 total revisions
88 1 files, 1 changesets, 1 total revisions
97
89
98 At the end...
90 At the end...
99
91
100 $ cd ..
92 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now