##// END OF EJS Templates
dispatch: protect against malicious 'hg serve --stdio' invocations (sec)...
Augie Fackler -
r32050:77eaf953 4.1.3 stable
parent child Browse files
Show More
@@ -1,86 +1,87 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 #
2 #
3 # Copyright 2005-2007 by Intevation GmbH <intevation@intevation.de>
3 # Copyright 2005-2007 by Intevation GmbH <intevation@intevation.de>
4 #
4 #
5 # Author(s):
5 # Author(s):
6 # Thomas Arendsen Hein <thomas@intevation.de>
6 # Thomas Arendsen Hein <thomas@intevation.de>
7 #
7 #
8 # This software may be used and distributed according to the terms of the
8 # This software may be used and distributed according to the terms of the
9 # GNU General Public License version 2 or any later version.
9 # GNU General Public License version 2 or any later version.
10
10
11 """
11 """
12 hg-ssh - a wrapper for ssh access to a limited set of mercurial repos
12 hg-ssh - a wrapper for ssh access to a limited set of mercurial repos
13
13
14 To be used in ~/.ssh/authorized_keys with the "command" option, see sshd(8):
14 To be used in ~/.ssh/authorized_keys with the "command" option, see sshd(8):
15 command="hg-ssh path/to/repo1 /path/to/repo2 ~/repo3 ~user/repo4" ssh-dss ...
15 command="hg-ssh path/to/repo1 /path/to/repo2 ~/repo3 ~user/repo4" ssh-dss ...
16 (probably together with these other useful options:
16 (probably together with these other useful options:
17 no-port-forwarding,no-X11-forwarding,no-agent-forwarding)
17 no-port-forwarding,no-X11-forwarding,no-agent-forwarding)
18
18
19 This allows pull/push over ssh from/to the repositories given as arguments.
19 This allows pull/push over ssh from/to the repositories given as arguments.
20
20
21 If all your repositories are subdirectories of a common directory, you can
21 If all your repositories are subdirectories of a common directory, you can
22 allow shorter paths with:
22 allow shorter paths with:
23 command="cd path/to/my/repositories && hg-ssh repo1 subdir/repo2"
23 command="cd path/to/my/repositories && hg-ssh repo1 subdir/repo2"
24
24
25 You can use pattern matching of your normal shell, e.g.:
25 You can use pattern matching of your normal shell, e.g.:
26 command="cd repos && hg-ssh user/thomas/* projects/{mercurial,foo}"
26 command="cd repos && hg-ssh user/thomas/* projects/{mercurial,foo}"
27
27
28 You can also add a --read-only flag to allow read-only access to a key, e.g.:
28 You can also add a --read-only flag to allow read-only access to a key, e.g.:
29 command="hg-ssh --read-only repos/*"
29 command="hg-ssh --read-only repos/*"
30 """
30 """
31
31
32 # enable importing on demand to reduce startup time
32 # enable importing on demand to reduce startup time
33 from mercurial import demandimport; demandimport.enable()
33 from mercurial import demandimport; demandimport.enable()
34
34
35 from mercurial import dispatch
35 from mercurial import dispatch, ui as uimod
36
36
37 import sys, os, shlex
37 import sys, os, shlex
38
38
39 def main():
39 def main():
40 cwd = os.getcwd()
40 cwd = os.getcwd()
41 readonly = False
41 readonly = False
42 args = sys.argv[1:]
42 args = sys.argv[1:]
43 while len(args):
43 while len(args):
44 if args[0] == '--read-only':
44 if args[0] == '--read-only':
45 readonly = True
45 readonly = True
46 args.pop(0)
46 args.pop(0)
47 else:
47 else:
48 break
48 break
49 allowed_paths = [os.path.normpath(os.path.join(cwd,
49 allowed_paths = [os.path.normpath(os.path.join(cwd,
50 os.path.expanduser(path)))
50 os.path.expanduser(path)))
51 for path in args]
51 for path in args]
52 orig_cmd = os.getenv('SSH_ORIGINAL_COMMAND', '?')
52 orig_cmd = os.getenv('SSH_ORIGINAL_COMMAND', '?')
53 try:
53 try:
54 cmdargv = shlex.split(orig_cmd)
54 cmdargv = shlex.split(orig_cmd)
55 except ValueError as e:
55 except ValueError as e:
56 sys.stderr.write('Illegal command "%s": %s\n' % (orig_cmd, e))
56 sys.stderr.write('Illegal command "%s": %s\n' % (orig_cmd, e))
57 sys.exit(255)
57 sys.exit(255)
58
58
59 if cmdargv[:2] == ['hg', '-R'] and cmdargv[3:] == ['serve', '--stdio']:
59 if cmdargv[:2] == ['hg', '-R'] and cmdargv[3:] == ['serve', '--stdio']:
60 path = cmdargv[2]
60 path = cmdargv[2]
61 repo = os.path.normpath(os.path.join(cwd, os.path.expanduser(path)))
61 repo = os.path.normpath(os.path.join(cwd, os.path.expanduser(path)))
62 if repo in allowed_paths:
62 if repo in allowed_paths:
63 cmd = ['-R', repo, 'serve', '--stdio']
63 cmd = ['-R', repo, 'serve', '--stdio']
64 req = dispatch.request(cmd)
64 if readonly:
65 if readonly:
65 cmd += [
66 if not req.ui:
66 '--config',
67 req.ui = uimod.ui.load()
67 'hooks.pretxnopen.hg-ssh=python:__main__.rejectpush',
68 req.ui.setconfig('hooks', 'pretxnopen.hg-ssh',
68 '--config',
69 'python:__main__.rejectpush', 'hg-ssh')
69 'hooks.prepushkey.hg-ssh=python:__main__.rejectpush'
70 req.ui.setconfig('hooks', 'prepushkey.hg-ssh',
70 ]
71 'python:__main__.rejectpush', 'hg-ssh')
71 dispatch.dispatch(dispatch.request(cmd))
72 dispatch.dispatch(req)
72 else:
73 else:
73 sys.stderr.write('Illegal repository "%s"\n' % repo)
74 sys.stderr.write('Illegal repository "%s"\n' % repo)
74 sys.exit(255)
75 sys.exit(255)
75 else:
76 else:
76 sys.stderr.write('Illegal command "%s"\n' % orig_cmd)
77 sys.stderr.write('Illegal command "%s"\n' % orig_cmd)
77 sys.exit(255)
78 sys.exit(255)
78
79
79 def rejectpush(ui, **kwargs):
80 def rejectpush(ui, **kwargs):
80 ui.warn(("Permission denied\n"))
81 ui.warn(("Permission denied\n"))
81 # mercurial hooks use unix process conventions for hook return values
82 # mercurial hooks use unix process conventions for hook return values
82 # so a truthy return means failure
83 # so a truthy return means failure
83 return True
84 return True
84
85
85 if __name__ == '__main__':
86 if __name__ == '__main__':
86 main()
87 main()
@@ -1,888 +1,919 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 atexit
10 import atexit
11 import difflib
11 import difflib
12 import errno
12 import errno
13 import getopt
13 import getopt
14 import os
14 import os
15 import pdb
15 import pdb
16 import re
16 import re
17 import signal
17 import signal
18 import sys
18 import sys
19 import time
19 import time
20 import traceback
20 import traceback
21
21
22
22
23 from .i18n import _
23 from .i18n import _
24
24
25 from . import (
25 from . import (
26 cmdutil,
26 cmdutil,
27 color,
27 color,
28 commands,
28 commands,
29 debugcommands,
29 debugcommands,
30 demandimport,
30 demandimport,
31 encoding,
31 encoding,
32 error,
32 error,
33 extensions,
33 extensions,
34 fancyopts,
34 fancyopts,
35 fileset,
35 fileset,
36 hg,
36 hg,
37 hook,
37 hook,
38 profiling,
38 profiling,
39 pycompat,
39 pycompat,
40 revset,
40 revset,
41 scmutil,
41 scmutil,
42 templatefilters,
42 templatefilters,
43 templatekw,
43 templatekw,
44 templater,
44 templater,
45 ui as uimod,
45 ui as uimod,
46 util,
46 util,
47 )
47 )
48
48
49 class request(object):
49 class request(object):
50 def __init__(self, args, ui=None, repo=None, fin=None, fout=None,
50 def __init__(self, args, ui=None, repo=None, fin=None, fout=None,
51 ferr=None):
51 ferr=None):
52 self.args = args
52 self.args = args
53 self.ui = ui
53 self.ui = ui
54 self.repo = repo
54 self.repo = repo
55
55
56 # input/output/error streams
56 # input/output/error streams
57 self.fin = fin
57 self.fin = fin
58 self.fout = fout
58 self.fout = fout
59 self.ferr = ferr
59 self.ferr = ferr
60
60
61 def run():
61 def run():
62 "run the command in sys.argv"
62 "run the command in sys.argv"
63 sys.exit((dispatch(request(pycompat.sysargv[1:])) or 0) & 255)
63 sys.exit((dispatch(request(pycompat.sysargv[1:])) or 0) & 255)
64
64
65 def _getsimilar(symbols, value):
65 def _getsimilar(symbols, value):
66 sim = lambda x: difflib.SequenceMatcher(None, value, x).ratio()
66 sim = lambda x: difflib.SequenceMatcher(None, value, x).ratio()
67 # The cutoff for similarity here is pretty arbitrary. It should
67 # The cutoff for similarity here is pretty arbitrary. It should
68 # probably be investigated and tweaked.
68 # probably be investigated and tweaked.
69 return [s for s in symbols if sim(s) > 0.6]
69 return [s for s in symbols if sim(s) > 0.6]
70
70
71 def _reportsimilar(write, similar):
71 def _reportsimilar(write, similar):
72 if len(similar) == 1:
72 if len(similar) == 1:
73 write(_("(did you mean %s?)\n") % similar[0])
73 write(_("(did you mean %s?)\n") % similar[0])
74 elif similar:
74 elif similar:
75 ss = ", ".join(sorted(similar))
75 ss = ", ".join(sorted(similar))
76 write(_("(did you mean one of %s?)\n") % ss)
76 write(_("(did you mean one of %s?)\n") % ss)
77
77
78 def _formatparse(write, inst):
78 def _formatparse(write, inst):
79 similar = []
79 similar = []
80 if isinstance(inst, error.UnknownIdentifier):
80 if isinstance(inst, error.UnknownIdentifier):
81 # make sure to check fileset first, as revset can invoke fileset
81 # make sure to check fileset first, as revset can invoke fileset
82 similar = _getsimilar(inst.symbols, inst.function)
82 similar = _getsimilar(inst.symbols, inst.function)
83 if len(inst.args) > 1:
83 if len(inst.args) > 1:
84 write(_("hg: parse error at %s: %s\n") %
84 write(_("hg: parse error at %s: %s\n") %
85 (inst.args[1], inst.args[0]))
85 (inst.args[1], inst.args[0]))
86 if (inst.args[0][0] == ' '):
86 if (inst.args[0][0] == ' '):
87 write(_("unexpected leading whitespace\n"))
87 write(_("unexpected leading whitespace\n"))
88 else:
88 else:
89 write(_("hg: parse error: %s\n") % inst.args[0])
89 write(_("hg: parse error: %s\n") % inst.args[0])
90 _reportsimilar(write, similar)
90 _reportsimilar(write, similar)
91 if inst.hint:
91 if inst.hint:
92 write(_("(%s)\n") % inst.hint)
92 write(_("(%s)\n") % inst.hint)
93
93
94 def dispatch(req):
94 def dispatch(req):
95 "run the command specified in req.args"
95 "run the command specified in req.args"
96 if req.ferr:
96 if req.ferr:
97 ferr = req.ferr
97 ferr = req.ferr
98 elif req.ui:
98 elif req.ui:
99 ferr = req.ui.ferr
99 ferr = req.ui.ferr
100 else:
100 else:
101 ferr = util.stderr
101 ferr = util.stderr
102
102
103 try:
103 try:
104 if not req.ui:
104 if not req.ui:
105 req.ui = uimod.ui.load()
105 req.ui = uimod.ui.load()
106 if '--traceback' in req.args:
106 if '--traceback' in req.args:
107 req.ui.setconfig('ui', 'traceback', 'on', '--traceback')
107 req.ui.setconfig('ui', 'traceback', 'on', '--traceback')
108
108
109 # set ui streams from the request
109 # set ui streams from the request
110 if req.fin:
110 if req.fin:
111 req.ui.fin = req.fin
111 req.ui.fin = req.fin
112 if req.fout:
112 if req.fout:
113 req.ui.fout = req.fout
113 req.ui.fout = req.fout
114 if req.ferr:
114 if req.ferr:
115 req.ui.ferr = req.ferr
115 req.ui.ferr = req.ferr
116 except error.Abort as inst:
116 except error.Abort as inst:
117 ferr.write(_("abort: %s\n") % inst)
117 ferr.write(_("abort: %s\n") % inst)
118 if inst.hint:
118 if inst.hint:
119 ferr.write(_("(%s)\n") % inst.hint)
119 ferr.write(_("(%s)\n") % inst.hint)
120 return -1
120 return -1
121 except error.ParseError as inst:
121 except error.ParseError as inst:
122 _formatparse(ferr.write, inst)
122 _formatparse(ferr.write, inst)
123 return -1
123 return -1
124
124
125 msg = ' '.join(' ' in a and repr(a) or a for a in req.args)
125 msg = ' '.join(' ' in a and repr(a) or a for a in req.args)
126 starttime = time.time()
126 starttime = time.time()
127 ret = None
127 ret = None
128 try:
128 try:
129 ret = _runcatch(req)
129 ret = _runcatch(req)
130 except KeyboardInterrupt:
130 except KeyboardInterrupt:
131 try:
131 try:
132 req.ui.warn(_("interrupted!\n"))
132 req.ui.warn(_("interrupted!\n"))
133 except IOError as inst:
133 except IOError as inst:
134 if inst.errno != errno.EPIPE:
134 if inst.errno != errno.EPIPE:
135 raise
135 raise
136 ret = -1
136 ret = -1
137 finally:
137 finally:
138 duration = time.time() - starttime
138 duration = time.time() - starttime
139 req.ui.flush()
139 req.ui.flush()
140 req.ui.log("commandfinish", "%s exited %s after %0.2f seconds\n",
140 req.ui.log("commandfinish", "%s exited %s after %0.2f seconds\n",
141 msg, ret or 0, duration)
141 msg, ret or 0, duration)
142 return ret
142 return ret
143
143
144 def _runcatch(req):
144 def _runcatch(req):
145 def catchterm(*args):
145 def catchterm(*args):
146 raise error.SignalInterrupt
146 raise error.SignalInterrupt
147
147
148 ui = req.ui
148 ui = req.ui
149 try:
149 try:
150 for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM':
150 for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM':
151 num = getattr(signal, name, None)
151 num = getattr(signal, name, None)
152 if num:
152 if num:
153 signal.signal(num, catchterm)
153 signal.signal(num, catchterm)
154 except ValueError:
154 except ValueError:
155 pass # happens if called in a thread
155 pass # happens if called in a thread
156
156
157 def _runcatchfunc():
157 def _runcatchfunc():
158 realcmd = None
159 try:
160 cmdargs = fancyopts.fancyopts(req.args[:], commands.globalopts, {})
161 cmd = cmdargs[0]
162 aliases, entry = cmdutil.findcmd(cmd, commands.table, False)
163 realcmd = aliases[0]
164 except (error.UnknownCommand, error.AmbiguousCommand,
165 IndexError, getopt.GetoptError):
166 # Don't handle this here. We know the command is
167 # invalid, but all we're worried about for now is that
168 # it's not a command that server operators expect to
169 # be safe to offer to users in a sandbox.
170 pass
171 if realcmd == 'serve' and '--stdio' in cmdargs:
172 # We want to constrain 'hg serve --stdio' instances pretty
173 # closely, as many shared-ssh access tools want to grant
174 # access to run *only* 'hg -R $repo serve --stdio'. We
175 # restrict to exactly that set of arguments, and prohibit
176 # any repo name that starts with '--' to prevent
177 # shenanigans wherein a user does something like pass
178 # --debugger or --config=ui.debugger=1 as a repo
179 # name. This used to actually run the debugger.
180 if (len(req.args) != 4 or
181 req.args[0] != '-R' or
182 req.args[1].startswith('--') or
183 req.args[2] != 'serve' or
184 req.args[3] != '--stdio'):
185 raise error.Abort(
186 _('potentially unsafe serve --stdio invocation: %r') %
187 (req.args,))
188
158 try:
189 try:
159 debugger = 'pdb'
190 debugger = 'pdb'
160 debugtrace = {
191 debugtrace = {
161 'pdb' : pdb.set_trace
192 'pdb' : pdb.set_trace
162 }
193 }
163 debugmortem = {
194 debugmortem = {
164 'pdb' : pdb.post_mortem
195 'pdb' : pdb.post_mortem
165 }
196 }
166
197
167 # read --config before doing anything else
198 # read --config before doing anything else
168 # (e.g. to change trust settings for reading .hg/hgrc)
199 # (e.g. to change trust settings for reading .hg/hgrc)
169 cfgs = _parseconfig(req.ui, _earlygetopt(['--config'], req.args))
200 cfgs = _parseconfig(req.ui, _earlygetopt(['--config'], req.args))
170
201
171 if req.repo:
202 if req.repo:
172 # copy configs that were passed on the cmdline (--config) to
203 # copy configs that were passed on the cmdline (--config) to
173 # the repo ui
204 # the repo ui
174 for sec, name, val in cfgs:
205 for sec, name, val in cfgs:
175 req.repo.ui.setconfig(sec, name, val, source='--config')
206 req.repo.ui.setconfig(sec, name, val, source='--config')
176
207
177 # developer config: ui.debugger
208 # developer config: ui.debugger
178 debugger = ui.config("ui", "debugger")
209 debugger = ui.config("ui", "debugger")
179 debugmod = pdb
210 debugmod = pdb
180 if not debugger or ui.plain():
211 if not debugger or ui.plain():
181 # if we are in HGPLAIN mode, then disable custom debugging
212 # if we are in HGPLAIN mode, then disable custom debugging
182 debugger = 'pdb'
213 debugger = 'pdb'
183 elif '--debugger' in req.args:
214 elif '--debugger' in req.args:
184 # This import can be slow for fancy debuggers, so only
215 # This import can be slow for fancy debuggers, so only
185 # do it when absolutely necessary, i.e. when actual
216 # do it when absolutely necessary, i.e. when actual
186 # debugging has been requested
217 # debugging has been requested
187 with demandimport.deactivated():
218 with demandimport.deactivated():
188 try:
219 try:
189 debugmod = __import__(debugger)
220 debugmod = __import__(debugger)
190 except ImportError:
221 except ImportError:
191 pass # Leave debugmod = pdb
222 pass # Leave debugmod = pdb
192
223
193 debugtrace[debugger] = debugmod.set_trace
224 debugtrace[debugger] = debugmod.set_trace
194 debugmortem[debugger] = debugmod.post_mortem
225 debugmortem[debugger] = debugmod.post_mortem
195
226
196 # enter the debugger before command execution
227 # enter the debugger before command execution
197 if '--debugger' in req.args:
228 if '--debugger' in req.args:
198 ui.warn(_("entering debugger - "
229 ui.warn(_("entering debugger - "
199 "type c to continue starting hg or h for help\n"))
230 "type c to continue starting hg or h for help\n"))
200
231
201 if (debugger != 'pdb' and
232 if (debugger != 'pdb' and
202 debugtrace[debugger] == debugtrace['pdb']):
233 debugtrace[debugger] == debugtrace['pdb']):
203 ui.warn(_("%s debugger specified "
234 ui.warn(_("%s debugger specified "
204 "but its module was not found\n") % debugger)
235 "but its module was not found\n") % debugger)
205 with demandimport.deactivated():
236 with demandimport.deactivated():
206 debugtrace[debugger]()
237 debugtrace[debugger]()
207 try:
238 try:
208 return _dispatch(req)
239 return _dispatch(req)
209 finally:
240 finally:
210 ui.flush()
241 ui.flush()
211 except: # re-raises
242 except: # re-raises
212 # enter the debugger when we hit an exception
243 # enter the debugger when we hit an exception
213 if '--debugger' in req.args:
244 if '--debugger' in req.args:
214 traceback.print_exc()
245 traceback.print_exc()
215 debugmortem[debugger](sys.exc_info()[2])
246 debugmortem[debugger](sys.exc_info()[2])
216 ui.traceback()
247 ui.traceback()
217 raise
248 raise
218
249
219 return callcatch(ui, _runcatchfunc)
250 return callcatch(ui, _runcatchfunc)
220
251
221 def callcatch(ui, func):
252 def callcatch(ui, func):
222 """like scmutil.callcatch but handles more high-level exceptions about
253 """like scmutil.callcatch but handles more high-level exceptions about
223 config parsing and commands. besides, use handlecommandexception to handle
254 config parsing and commands. besides, use handlecommandexception to handle
224 uncaught exceptions.
255 uncaught exceptions.
225 """
256 """
226 try:
257 try:
227 return scmutil.callcatch(ui, func)
258 return scmutil.callcatch(ui, func)
228 except error.AmbiguousCommand as inst:
259 except error.AmbiguousCommand as inst:
229 ui.warn(_("hg: command '%s' is ambiguous:\n %s\n") %
260 ui.warn(_("hg: command '%s' is ambiguous:\n %s\n") %
230 (inst.args[0], " ".join(inst.args[1])))
261 (inst.args[0], " ".join(inst.args[1])))
231 except error.CommandError as inst:
262 except error.CommandError as inst:
232 if inst.args[0]:
263 if inst.args[0]:
233 ui.warn(_("hg %s: %s\n") % (inst.args[0], inst.args[1]))
264 ui.warn(_("hg %s: %s\n") % (inst.args[0], inst.args[1]))
234 commands.help_(ui, inst.args[0], full=False, command=True)
265 commands.help_(ui, inst.args[0], full=False, command=True)
235 else:
266 else:
236 ui.warn(_("hg: %s\n") % inst.args[1])
267 ui.warn(_("hg: %s\n") % inst.args[1])
237 commands.help_(ui, 'shortlist')
268 commands.help_(ui, 'shortlist')
238 except error.ParseError as inst:
269 except error.ParseError as inst:
239 _formatparse(ui.warn, inst)
270 _formatparse(ui.warn, inst)
240 return -1
271 return -1
241 except error.UnknownCommand as inst:
272 except error.UnknownCommand as inst:
242 ui.warn(_("hg: unknown command '%s'\n") % inst.args[0])
273 ui.warn(_("hg: unknown command '%s'\n") % inst.args[0])
243 try:
274 try:
244 # check if the command is in a disabled extension
275 # check if the command is in a disabled extension
245 # (but don't check for extensions themselves)
276 # (but don't check for extensions themselves)
246 commands.help_(ui, inst.args[0], unknowncmd=True)
277 commands.help_(ui, inst.args[0], unknowncmd=True)
247 except (error.UnknownCommand, error.Abort):
278 except (error.UnknownCommand, error.Abort):
248 suggested = False
279 suggested = False
249 if len(inst.args) == 2:
280 if len(inst.args) == 2:
250 sim = _getsimilar(inst.args[1], inst.args[0])
281 sim = _getsimilar(inst.args[1], inst.args[0])
251 if sim:
282 if sim:
252 _reportsimilar(ui.warn, sim)
283 _reportsimilar(ui.warn, sim)
253 suggested = True
284 suggested = True
254 if not suggested:
285 if not suggested:
255 commands.help_(ui, 'shortlist')
286 commands.help_(ui, 'shortlist')
256 except IOError:
287 except IOError:
257 raise
288 raise
258 except KeyboardInterrupt:
289 except KeyboardInterrupt:
259 raise
290 raise
260 except: # probably re-raises
291 except: # probably re-raises
261 if not handlecommandexception(ui):
292 if not handlecommandexception(ui):
262 raise
293 raise
263
294
264 return -1
295 return -1
265
296
266 def aliasargs(fn, givenargs):
297 def aliasargs(fn, givenargs):
267 args = getattr(fn, 'args', [])
298 args = getattr(fn, 'args', [])
268 if args:
299 if args:
269 cmd = ' '.join(map(util.shellquote, args))
300 cmd = ' '.join(map(util.shellquote, args))
270
301
271 nums = []
302 nums = []
272 def replacer(m):
303 def replacer(m):
273 num = int(m.group(1)) - 1
304 num = int(m.group(1)) - 1
274 nums.append(num)
305 nums.append(num)
275 if num < len(givenargs):
306 if num < len(givenargs):
276 return givenargs[num]
307 return givenargs[num]
277 raise error.Abort(_('too few arguments for command alias'))
308 raise error.Abort(_('too few arguments for command alias'))
278 cmd = re.sub(r'\$(\d+|\$)', replacer, cmd)
309 cmd = re.sub(r'\$(\d+|\$)', replacer, cmd)
279 givenargs = [x for i, x in enumerate(givenargs)
310 givenargs = [x for i, x in enumerate(givenargs)
280 if i not in nums]
311 if i not in nums]
281 args = pycompat.shlexsplit(cmd)
312 args = pycompat.shlexsplit(cmd)
282 return args + givenargs
313 return args + givenargs
283
314
284 def aliasinterpolate(name, args, cmd):
315 def aliasinterpolate(name, args, cmd):
285 '''interpolate args into cmd for shell aliases
316 '''interpolate args into cmd for shell aliases
286
317
287 This also handles $0, $@ and "$@".
318 This also handles $0, $@ and "$@".
288 '''
319 '''
289 # util.interpolate can't deal with "$@" (with quotes) because it's only
320 # util.interpolate can't deal with "$@" (with quotes) because it's only
290 # built to match prefix + patterns.
321 # built to match prefix + patterns.
291 replacemap = dict(('$%d' % (i + 1), arg) for i, arg in enumerate(args))
322 replacemap = dict(('$%d' % (i + 1), arg) for i, arg in enumerate(args))
292 replacemap['$0'] = name
323 replacemap['$0'] = name
293 replacemap['$$'] = '$'
324 replacemap['$$'] = '$'
294 replacemap['$@'] = ' '.join(args)
325 replacemap['$@'] = ' '.join(args)
295 # Typical Unix shells interpolate "$@" (with quotes) as all the positional
326 # Typical Unix shells interpolate "$@" (with quotes) as all the positional
296 # parameters, separated out into words. Emulate the same behavior here by
327 # parameters, separated out into words. Emulate the same behavior here by
297 # quoting the arguments individually. POSIX shells will then typically
328 # quoting the arguments individually. POSIX shells will then typically
298 # tokenize each argument into exactly one word.
329 # tokenize each argument into exactly one word.
299 replacemap['"$@"'] = ' '.join(util.shellquote(arg) for arg in args)
330 replacemap['"$@"'] = ' '.join(util.shellquote(arg) for arg in args)
300 # escape '\$' for regex
331 # escape '\$' for regex
301 regex = '|'.join(replacemap.keys()).replace('$', r'\$')
332 regex = '|'.join(replacemap.keys()).replace('$', r'\$')
302 r = re.compile(regex)
333 r = re.compile(regex)
303 return r.sub(lambda x: replacemap[x.group()], cmd)
334 return r.sub(lambda x: replacemap[x.group()], cmd)
304
335
305 class cmdalias(object):
336 class cmdalias(object):
306 def __init__(self, name, definition, cmdtable, source):
337 def __init__(self, name, definition, cmdtable, source):
307 self.name = self.cmd = name
338 self.name = self.cmd = name
308 self.cmdname = ''
339 self.cmdname = ''
309 self.definition = definition
340 self.definition = definition
310 self.fn = None
341 self.fn = None
311 self.givenargs = []
342 self.givenargs = []
312 self.opts = []
343 self.opts = []
313 self.help = ''
344 self.help = ''
314 self.badalias = None
345 self.badalias = None
315 self.unknowncmd = False
346 self.unknowncmd = False
316 self.source = source
347 self.source = source
317
348
318 try:
349 try:
319 aliases, entry = cmdutil.findcmd(self.name, cmdtable)
350 aliases, entry = cmdutil.findcmd(self.name, cmdtable)
320 for alias, e in cmdtable.iteritems():
351 for alias, e in cmdtable.iteritems():
321 if e is entry:
352 if e is entry:
322 self.cmd = alias
353 self.cmd = alias
323 break
354 break
324 self.shadows = True
355 self.shadows = True
325 except error.UnknownCommand:
356 except error.UnknownCommand:
326 self.shadows = False
357 self.shadows = False
327
358
328 if not self.definition:
359 if not self.definition:
329 self.badalias = _("no definition for alias '%s'") % self.name
360 self.badalias = _("no definition for alias '%s'") % self.name
330 return
361 return
331
362
332 if self.definition.startswith('!'):
363 if self.definition.startswith('!'):
333 self.shell = True
364 self.shell = True
334 def fn(ui, *args):
365 def fn(ui, *args):
335 env = {'HG_ARGS': ' '.join((self.name,) + args)}
366 env = {'HG_ARGS': ' '.join((self.name,) + args)}
336 def _checkvar(m):
367 def _checkvar(m):
337 if m.groups()[0] == '$':
368 if m.groups()[0] == '$':
338 return m.group()
369 return m.group()
339 elif int(m.groups()[0]) <= len(args):
370 elif int(m.groups()[0]) <= len(args):
340 return m.group()
371 return m.group()
341 else:
372 else:
342 ui.debug("No argument found for substitution "
373 ui.debug("No argument found for substitution "
343 "of %i variable in alias '%s' definition."
374 "of %i variable in alias '%s' definition."
344 % (int(m.groups()[0]), self.name))
375 % (int(m.groups()[0]), self.name))
345 return ''
376 return ''
346 cmd = re.sub(r'\$(\d+|\$)', _checkvar, self.definition[1:])
377 cmd = re.sub(r'\$(\d+|\$)', _checkvar, self.definition[1:])
347 cmd = aliasinterpolate(self.name, args, cmd)
378 cmd = aliasinterpolate(self.name, args, cmd)
348 return ui.system(cmd, environ=env)
379 return ui.system(cmd, environ=env)
349 self.fn = fn
380 self.fn = fn
350 return
381 return
351
382
352 try:
383 try:
353 args = pycompat.shlexsplit(self.definition)
384 args = pycompat.shlexsplit(self.definition)
354 except ValueError as inst:
385 except ValueError as inst:
355 self.badalias = (_("error in definition for alias '%s': %s")
386 self.badalias = (_("error in definition for alias '%s': %s")
356 % (self.name, inst))
387 % (self.name, inst))
357 return
388 return
358 self.cmdname = cmd = args.pop(0)
389 self.cmdname = cmd = args.pop(0)
359 self.givenargs = args
390 self.givenargs = args
360
391
361 for invalidarg in ("--cwd", "-R", "--repository", "--repo", "--config"):
392 for invalidarg in ("--cwd", "-R", "--repository", "--repo", "--config"):
362 if _earlygetopt([invalidarg], args):
393 if _earlygetopt([invalidarg], args):
363 self.badalias = (_("error in definition for alias '%s': %s may "
394 self.badalias = (_("error in definition for alias '%s': %s may "
364 "only be given on the command line")
395 "only be given on the command line")
365 % (self.name, invalidarg))
396 % (self.name, invalidarg))
366 return
397 return
367
398
368 try:
399 try:
369 tableentry = cmdutil.findcmd(cmd, cmdtable, False)[1]
400 tableentry = cmdutil.findcmd(cmd, cmdtable, False)[1]
370 if len(tableentry) > 2:
401 if len(tableentry) > 2:
371 self.fn, self.opts, self.help = tableentry
402 self.fn, self.opts, self.help = tableentry
372 else:
403 else:
373 self.fn, self.opts = tableentry
404 self.fn, self.opts = tableentry
374
405
375 if self.help.startswith("hg " + cmd):
406 if self.help.startswith("hg " + cmd):
376 # drop prefix in old-style help lines so hg shows the alias
407 # drop prefix in old-style help lines so hg shows the alias
377 self.help = self.help[4 + len(cmd):]
408 self.help = self.help[4 + len(cmd):]
378 self.__doc__ = self.fn.__doc__
409 self.__doc__ = self.fn.__doc__
379
410
380 except error.UnknownCommand:
411 except error.UnknownCommand:
381 self.badalias = (_("alias '%s' resolves to unknown command '%s'")
412 self.badalias = (_("alias '%s' resolves to unknown command '%s'")
382 % (self.name, cmd))
413 % (self.name, cmd))
383 self.unknowncmd = True
414 self.unknowncmd = True
384 except error.AmbiguousCommand:
415 except error.AmbiguousCommand:
385 self.badalias = (_("alias '%s' resolves to ambiguous command '%s'")
416 self.badalias = (_("alias '%s' resolves to ambiguous command '%s'")
386 % (self.name, cmd))
417 % (self.name, cmd))
387
418
388 @property
419 @property
389 def args(self):
420 def args(self):
390 args = map(util.expandpath, self.givenargs)
421 args = map(util.expandpath, self.givenargs)
391 return aliasargs(self.fn, args)
422 return aliasargs(self.fn, args)
392
423
393 def __getattr__(self, name):
424 def __getattr__(self, name):
394 adefaults = {'norepo': True, 'optionalrepo': False, 'inferrepo': False}
425 adefaults = {'norepo': True, 'optionalrepo': False, 'inferrepo': False}
395 if name not in adefaults:
426 if name not in adefaults:
396 raise AttributeError(name)
427 raise AttributeError(name)
397 if self.badalias or util.safehasattr(self, 'shell'):
428 if self.badalias or util.safehasattr(self, 'shell'):
398 return adefaults[name]
429 return adefaults[name]
399 return getattr(self.fn, name)
430 return getattr(self.fn, name)
400
431
401 def __call__(self, ui, *args, **opts):
432 def __call__(self, ui, *args, **opts):
402 if self.badalias:
433 if self.badalias:
403 hint = None
434 hint = None
404 if self.unknowncmd:
435 if self.unknowncmd:
405 try:
436 try:
406 # check if the command is in a disabled extension
437 # check if the command is in a disabled extension
407 cmd, ext = extensions.disabledcmd(ui, self.cmdname)[:2]
438 cmd, ext = extensions.disabledcmd(ui, self.cmdname)[:2]
408 hint = _("'%s' is provided by '%s' extension") % (cmd, ext)
439 hint = _("'%s' is provided by '%s' extension") % (cmd, ext)
409 except error.UnknownCommand:
440 except error.UnknownCommand:
410 pass
441 pass
411 raise error.Abort(self.badalias, hint=hint)
442 raise error.Abort(self.badalias, hint=hint)
412 if self.shadows:
443 if self.shadows:
413 ui.debug("alias '%s' shadows command '%s'\n" %
444 ui.debug("alias '%s' shadows command '%s'\n" %
414 (self.name, self.cmdname))
445 (self.name, self.cmdname))
415
446
416 ui.log('commandalias', "alias '%s' expands to '%s'\n",
447 ui.log('commandalias', "alias '%s' expands to '%s'\n",
417 self.name, self.definition)
448 self.name, self.definition)
418 if util.safehasattr(self, 'shell'):
449 if util.safehasattr(self, 'shell'):
419 return self.fn(ui, *args, **opts)
450 return self.fn(ui, *args, **opts)
420 else:
451 else:
421 try:
452 try:
422 return util.checksignature(self.fn)(ui, *args, **opts)
453 return util.checksignature(self.fn)(ui, *args, **opts)
423 except error.SignatureError:
454 except error.SignatureError:
424 args = ' '.join([self.cmdname] + self.args)
455 args = ' '.join([self.cmdname] + self.args)
425 ui.debug("alias '%s' expands to '%s'\n" % (self.name, args))
456 ui.debug("alias '%s' expands to '%s'\n" % (self.name, args))
426 raise
457 raise
427
458
428 def addaliases(ui, cmdtable):
459 def addaliases(ui, cmdtable):
429 # aliases are processed after extensions have been loaded, so they
460 # aliases are processed after extensions have been loaded, so they
430 # may use extension commands. Aliases can also use other alias definitions,
461 # may use extension commands. Aliases can also use other alias definitions,
431 # but only if they have been defined prior to the current definition.
462 # but only if they have been defined prior to the current definition.
432 for alias, definition in ui.configitems('alias'):
463 for alias, definition in ui.configitems('alias'):
433 source = ui.configsource('alias', alias)
464 source = ui.configsource('alias', alias)
434 aliasdef = cmdalias(alias, definition, cmdtable, source)
465 aliasdef = cmdalias(alias, definition, cmdtable, source)
435
466
436 try:
467 try:
437 olddef = cmdtable[aliasdef.cmd][0]
468 olddef = cmdtable[aliasdef.cmd][0]
438 if olddef.definition == aliasdef.definition:
469 if olddef.definition == aliasdef.definition:
439 continue
470 continue
440 except (KeyError, AttributeError):
471 except (KeyError, AttributeError):
441 # definition might not exist or it might not be a cmdalias
472 # definition might not exist or it might not be a cmdalias
442 pass
473 pass
443
474
444 cmdtable[aliasdef.name] = (aliasdef, aliasdef.opts, aliasdef.help)
475 cmdtable[aliasdef.name] = (aliasdef, aliasdef.opts, aliasdef.help)
445
476
446 def _parse(ui, args):
477 def _parse(ui, args):
447 options = {}
478 options = {}
448 cmdoptions = {}
479 cmdoptions = {}
449
480
450 try:
481 try:
451 args = fancyopts.fancyopts(args, commands.globalopts, options)
482 args = fancyopts.fancyopts(args, commands.globalopts, options)
452 except getopt.GetoptError as inst:
483 except getopt.GetoptError as inst:
453 raise error.CommandError(None, inst)
484 raise error.CommandError(None, inst)
454
485
455 if args:
486 if args:
456 cmd, args = args[0], args[1:]
487 cmd, args = args[0], args[1:]
457 aliases, entry = cmdutil.findcmd(cmd, commands.table,
488 aliases, entry = cmdutil.findcmd(cmd, commands.table,
458 ui.configbool("ui", "strict"))
489 ui.configbool("ui", "strict"))
459 cmd = aliases[0]
490 cmd = aliases[0]
460 args = aliasargs(entry[0], args)
491 args = aliasargs(entry[0], args)
461 defaults = ui.config("defaults", cmd)
492 defaults = ui.config("defaults", cmd)
462 if defaults:
493 if defaults:
463 args = map(util.expandpath, pycompat.shlexsplit(defaults)) + args
494 args = map(util.expandpath, pycompat.shlexsplit(defaults)) + args
464 c = list(entry[1])
495 c = list(entry[1])
465 else:
496 else:
466 cmd = None
497 cmd = None
467 c = []
498 c = []
468
499
469 # combine global options into local
500 # combine global options into local
470 for o in commands.globalopts:
501 for o in commands.globalopts:
471 c.append((o[0], o[1], options[o[1]], o[3]))
502 c.append((o[0], o[1], options[o[1]], o[3]))
472
503
473 try:
504 try:
474 args = fancyopts.fancyopts(args, c, cmdoptions, gnu=True)
505 args = fancyopts.fancyopts(args, c, cmdoptions, gnu=True)
475 except getopt.GetoptError as inst:
506 except getopt.GetoptError as inst:
476 raise error.CommandError(cmd, inst)
507 raise error.CommandError(cmd, inst)
477
508
478 # separate global options back out
509 # separate global options back out
479 for o in commands.globalopts:
510 for o in commands.globalopts:
480 n = o[1]
511 n = o[1]
481 options[n] = cmdoptions[n]
512 options[n] = cmdoptions[n]
482 del cmdoptions[n]
513 del cmdoptions[n]
483
514
484 return (cmd, cmd and entry[0] or None, args, options, cmdoptions)
515 return (cmd, cmd and entry[0] or None, args, options, cmdoptions)
485
516
486 def _parseconfig(ui, config):
517 def _parseconfig(ui, config):
487 """parse the --config options from the command line"""
518 """parse the --config options from the command line"""
488 configs = []
519 configs = []
489
520
490 for cfg in config:
521 for cfg in config:
491 try:
522 try:
492 name, value = [cfgelem.strip()
523 name, value = [cfgelem.strip()
493 for cfgelem in cfg.split('=', 1)]
524 for cfgelem in cfg.split('=', 1)]
494 section, name = name.split('.', 1)
525 section, name = name.split('.', 1)
495 if not section or not name:
526 if not section or not name:
496 raise IndexError
527 raise IndexError
497 ui.setconfig(section, name, value, '--config')
528 ui.setconfig(section, name, value, '--config')
498 configs.append((section, name, value))
529 configs.append((section, name, value))
499 except (IndexError, ValueError):
530 except (IndexError, ValueError):
500 raise error.Abort(_('malformed --config option: %r '
531 raise error.Abort(_('malformed --config option: %r '
501 '(use --config section.name=value)') % cfg)
532 '(use --config section.name=value)') % cfg)
502
533
503 return configs
534 return configs
504
535
505 def _earlygetopt(aliases, args):
536 def _earlygetopt(aliases, args):
506 """Return list of values for an option (or aliases).
537 """Return list of values for an option (or aliases).
507
538
508 The values are listed in the order they appear in args.
539 The values are listed in the order they appear in args.
509 The options and values are removed from args.
540 The options and values are removed from args.
510
541
511 >>> args = ['x', '--cwd', 'foo', 'y']
542 >>> args = ['x', '--cwd', 'foo', 'y']
512 >>> _earlygetopt(['--cwd'], args), args
543 >>> _earlygetopt(['--cwd'], args), args
513 (['foo'], ['x', 'y'])
544 (['foo'], ['x', 'y'])
514
545
515 >>> args = ['x', '--cwd=bar', 'y']
546 >>> args = ['x', '--cwd=bar', 'y']
516 >>> _earlygetopt(['--cwd'], args), args
547 >>> _earlygetopt(['--cwd'], args), args
517 (['bar'], ['x', 'y'])
548 (['bar'], ['x', 'y'])
518
549
519 >>> args = ['x', '-R', 'foo', 'y']
550 >>> args = ['x', '-R', 'foo', 'y']
520 >>> _earlygetopt(['-R'], args), args
551 >>> _earlygetopt(['-R'], args), args
521 (['foo'], ['x', 'y'])
552 (['foo'], ['x', 'y'])
522
553
523 >>> args = ['x', '-Rbar', 'y']
554 >>> args = ['x', '-Rbar', 'y']
524 >>> _earlygetopt(['-R'], args), args
555 >>> _earlygetopt(['-R'], args), args
525 (['bar'], ['x', 'y'])
556 (['bar'], ['x', 'y'])
526 """
557 """
527 try:
558 try:
528 argcount = args.index("--")
559 argcount = args.index("--")
529 except ValueError:
560 except ValueError:
530 argcount = len(args)
561 argcount = len(args)
531 shortopts = [opt for opt in aliases if len(opt) == 2]
562 shortopts = [opt for opt in aliases if len(opt) == 2]
532 values = []
563 values = []
533 pos = 0
564 pos = 0
534 while pos < argcount:
565 while pos < argcount:
535 fullarg = arg = args[pos]
566 fullarg = arg = args[pos]
536 equals = arg.find('=')
567 equals = arg.find('=')
537 if equals > -1:
568 if equals > -1:
538 arg = arg[:equals]
569 arg = arg[:equals]
539 if arg in aliases:
570 if arg in aliases:
540 del args[pos]
571 del args[pos]
541 if equals > -1:
572 if equals > -1:
542 values.append(fullarg[equals + 1:])
573 values.append(fullarg[equals + 1:])
543 argcount -= 1
574 argcount -= 1
544 else:
575 else:
545 if pos + 1 >= argcount:
576 if pos + 1 >= argcount:
546 # ignore and let getopt report an error if there is no value
577 # ignore and let getopt report an error if there is no value
547 break
578 break
548 values.append(args.pop(pos))
579 values.append(args.pop(pos))
549 argcount -= 2
580 argcount -= 2
550 elif arg[:2] in shortopts:
581 elif arg[:2] in shortopts:
551 # short option can have no following space, e.g. hg log -Rfoo
582 # short option can have no following space, e.g. hg log -Rfoo
552 values.append(args.pop(pos)[2:])
583 values.append(args.pop(pos)[2:])
553 argcount -= 1
584 argcount -= 1
554 else:
585 else:
555 pos += 1
586 pos += 1
556 return values
587 return values
557
588
558 def runcommand(lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions):
589 def runcommand(lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions):
559 # run pre-hook, and abort if it fails
590 # run pre-hook, and abort if it fails
560 hook.hook(lui, repo, "pre-%s" % cmd, True, args=" ".join(fullargs),
591 hook.hook(lui, repo, "pre-%s" % cmd, True, args=" ".join(fullargs),
561 pats=cmdpats, opts=cmdoptions)
592 pats=cmdpats, opts=cmdoptions)
562 try:
593 try:
563 ret = _runcommand(ui, options, cmd, d)
594 ret = _runcommand(ui, options, cmd, d)
564 # run post-hook, passing command result
595 # run post-hook, passing command result
565 hook.hook(lui, repo, "post-%s" % cmd, False, args=" ".join(fullargs),
596 hook.hook(lui, repo, "post-%s" % cmd, False, args=" ".join(fullargs),
566 result=ret, pats=cmdpats, opts=cmdoptions)
597 result=ret, pats=cmdpats, opts=cmdoptions)
567 except Exception:
598 except Exception:
568 # run failure hook and re-raise
599 # run failure hook and re-raise
569 hook.hook(lui, repo, "fail-%s" % cmd, False, args=" ".join(fullargs),
600 hook.hook(lui, repo, "fail-%s" % cmd, False, args=" ".join(fullargs),
570 pats=cmdpats, opts=cmdoptions)
601 pats=cmdpats, opts=cmdoptions)
571 raise
602 raise
572 return ret
603 return ret
573
604
574 def _getlocal(ui, rpath, wd=None):
605 def _getlocal(ui, rpath, wd=None):
575 """Return (path, local ui object) for the given target path.
606 """Return (path, local ui object) for the given target path.
576
607
577 Takes paths in [cwd]/.hg/hgrc into account."
608 Takes paths in [cwd]/.hg/hgrc into account."
578 """
609 """
579 if wd is None:
610 if wd is None:
580 try:
611 try:
581 wd = pycompat.getcwd()
612 wd = pycompat.getcwd()
582 except OSError as e:
613 except OSError as e:
583 raise error.Abort(_("error getting current working directory: %s") %
614 raise error.Abort(_("error getting current working directory: %s") %
584 e.strerror)
615 e.strerror)
585 path = cmdutil.findrepo(wd) or ""
616 path = cmdutil.findrepo(wd) or ""
586 if not path:
617 if not path:
587 lui = ui
618 lui = ui
588 else:
619 else:
589 lui = ui.copy()
620 lui = ui.copy()
590 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
621 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
591
622
592 if rpath and rpath[-1]:
623 if rpath and rpath[-1]:
593 path = lui.expandpath(rpath[-1])
624 path = lui.expandpath(rpath[-1])
594 lui = ui.copy()
625 lui = ui.copy()
595 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
626 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
596
627
597 return path, lui
628 return path, lui
598
629
599 def _checkshellalias(lui, ui, args):
630 def _checkshellalias(lui, ui, args):
600 """Return the function to run the shell alias, if it is required"""
631 """Return the function to run the shell alias, if it is required"""
601 options = {}
632 options = {}
602
633
603 try:
634 try:
604 args = fancyopts.fancyopts(args, commands.globalopts, options)
635 args = fancyopts.fancyopts(args, commands.globalopts, options)
605 except getopt.GetoptError:
636 except getopt.GetoptError:
606 return
637 return
607
638
608 if not args:
639 if not args:
609 return
640 return
610
641
611 cmdtable = commands.table
642 cmdtable = commands.table
612
643
613 cmd = args[0]
644 cmd = args[0]
614 try:
645 try:
615 strict = ui.configbool("ui", "strict")
646 strict = ui.configbool("ui", "strict")
616 aliases, entry = cmdutil.findcmd(cmd, cmdtable, strict)
647 aliases, entry = cmdutil.findcmd(cmd, cmdtable, strict)
617 except (error.AmbiguousCommand, error.UnknownCommand):
648 except (error.AmbiguousCommand, error.UnknownCommand):
618 return
649 return
619
650
620 cmd = aliases[0]
651 cmd = aliases[0]
621 fn = entry[0]
652 fn = entry[0]
622
653
623 if cmd and util.safehasattr(fn, 'shell'):
654 if cmd and util.safehasattr(fn, 'shell'):
624 d = lambda: fn(ui, *args[1:])
655 d = lambda: fn(ui, *args[1:])
625 return lambda: runcommand(lui, None, cmd, args[:1], ui, options, d,
656 return lambda: runcommand(lui, None, cmd, args[:1], ui, options, d,
626 [], {})
657 [], {})
627
658
628 _loaded = set()
659 _loaded = set()
629
660
630 # list of (objname, loadermod, loadername) tuple:
661 # list of (objname, loadermod, loadername) tuple:
631 # - objname is the name of an object in extension module, from which
662 # - objname is the name of an object in extension module, from which
632 # extra information is loaded
663 # extra information is loaded
633 # - loadermod is the module where loader is placed
664 # - loadermod is the module where loader is placed
634 # - loadername is the name of the function, which takes (ui, extensionname,
665 # - loadername is the name of the function, which takes (ui, extensionname,
635 # extraobj) arguments
666 # extraobj) arguments
636 extraloaders = [
667 extraloaders = [
637 ('cmdtable', commands, 'loadcmdtable'),
668 ('cmdtable', commands, 'loadcmdtable'),
638 ('colortable', color, 'loadcolortable'),
669 ('colortable', color, 'loadcolortable'),
639 ('filesetpredicate', fileset, 'loadpredicate'),
670 ('filesetpredicate', fileset, 'loadpredicate'),
640 ('revsetpredicate', revset, 'loadpredicate'),
671 ('revsetpredicate', revset, 'loadpredicate'),
641 ('templatefilter', templatefilters, 'loadfilter'),
672 ('templatefilter', templatefilters, 'loadfilter'),
642 ('templatefunc', templater, 'loadfunction'),
673 ('templatefunc', templater, 'loadfunction'),
643 ('templatekeyword', templatekw, 'loadkeyword'),
674 ('templatekeyword', templatekw, 'loadkeyword'),
644 ]
675 ]
645
676
646 def _dispatch(req):
677 def _dispatch(req):
647 args = req.args
678 args = req.args
648 ui = req.ui
679 ui = req.ui
649
680
650 # check for cwd
681 # check for cwd
651 cwd = _earlygetopt(['--cwd'], args)
682 cwd = _earlygetopt(['--cwd'], args)
652 if cwd:
683 if cwd:
653 os.chdir(cwd[-1])
684 os.chdir(cwd[-1])
654
685
655 rpath = _earlygetopt(["-R", "--repository", "--repo"], args)
686 rpath = _earlygetopt(["-R", "--repository", "--repo"], args)
656 path, lui = _getlocal(ui, rpath)
687 path, lui = _getlocal(ui, rpath)
657
688
658 # Configure extensions in phases: uisetup, extsetup, cmdtable, and
689 # Configure extensions in phases: uisetup, extsetup, cmdtable, and
659 # reposetup. Programs like TortoiseHg will call _dispatch several
690 # reposetup. Programs like TortoiseHg will call _dispatch several
660 # times so we keep track of configured extensions in _loaded.
691 # times so we keep track of configured extensions in _loaded.
661 extensions.loadall(lui)
692 extensions.loadall(lui)
662 exts = [ext for ext in extensions.extensions() if ext[0] not in _loaded]
693 exts = [ext for ext in extensions.extensions() if ext[0] not in _loaded]
663 # Propagate any changes to lui.__class__ by extensions
694 # Propagate any changes to lui.__class__ by extensions
664 ui.__class__ = lui.__class__
695 ui.__class__ = lui.__class__
665
696
666 # (uisetup and extsetup are handled in extensions.loadall)
697 # (uisetup and extsetup are handled in extensions.loadall)
667
698
668 for name, module in exts:
699 for name, module in exts:
669 for objname, loadermod, loadername in extraloaders:
700 for objname, loadermod, loadername in extraloaders:
670 extraobj = getattr(module, objname, None)
701 extraobj = getattr(module, objname, None)
671 if extraobj is not None:
702 if extraobj is not None:
672 getattr(loadermod, loadername)(ui, name, extraobj)
703 getattr(loadermod, loadername)(ui, name, extraobj)
673 _loaded.add(name)
704 _loaded.add(name)
674
705
675 # (reposetup is handled in hg.repository)
706 # (reposetup is handled in hg.repository)
676
707
677 # Side-effect of accessing is debugcommands module is guaranteed to be
708 # Side-effect of accessing is debugcommands module is guaranteed to be
678 # imported and commands.table is populated.
709 # imported and commands.table is populated.
679 debugcommands.command
710 debugcommands.command
680
711
681 addaliases(lui, commands.table)
712 addaliases(lui, commands.table)
682
713
683 # All aliases and commands are completely defined, now.
714 # All aliases and commands are completely defined, now.
684 # Check abbreviation/ambiguity of shell alias.
715 # Check abbreviation/ambiguity of shell alias.
685 shellaliasfn = _checkshellalias(lui, ui, args)
716 shellaliasfn = _checkshellalias(lui, ui, args)
686 if shellaliasfn:
717 if shellaliasfn:
687 with profiling.maybeprofile(lui):
718 with profiling.maybeprofile(lui):
688 return shellaliasfn()
719 return shellaliasfn()
689
720
690 # check for fallback encoding
721 # check for fallback encoding
691 fallback = lui.config('ui', 'fallbackencoding')
722 fallback = lui.config('ui', 'fallbackencoding')
692 if fallback:
723 if fallback:
693 encoding.fallbackencoding = fallback
724 encoding.fallbackencoding = fallback
694
725
695 fullargs = args
726 fullargs = args
696 cmd, func, args, options, cmdoptions = _parse(lui, args)
727 cmd, func, args, options, cmdoptions = _parse(lui, args)
697
728
698 if options["config"]:
729 if options["config"]:
699 raise error.Abort(_("option --config may not be abbreviated!"))
730 raise error.Abort(_("option --config may not be abbreviated!"))
700 if options["cwd"]:
731 if options["cwd"]:
701 raise error.Abort(_("option --cwd may not be abbreviated!"))
732 raise error.Abort(_("option --cwd may not be abbreviated!"))
702 if options["repository"]:
733 if options["repository"]:
703 raise error.Abort(_(
734 raise error.Abort(_(
704 "option -R has to be separated from other options (e.g. not -qR) "
735 "option -R has to be separated from other options (e.g. not -qR) "
705 "and --repository may only be abbreviated as --repo!"))
736 "and --repository may only be abbreviated as --repo!"))
706
737
707 if options["encoding"]:
738 if options["encoding"]:
708 encoding.encoding = options["encoding"]
739 encoding.encoding = options["encoding"]
709 if options["encodingmode"]:
740 if options["encodingmode"]:
710 encoding.encodingmode = options["encodingmode"]
741 encoding.encodingmode = options["encodingmode"]
711 if options["time"]:
742 if options["time"]:
712 def get_times():
743 def get_times():
713 t = os.times()
744 t = os.times()
714 if t[4] == 0.0: # Windows leaves this as zero, so use time.clock()
745 if t[4] == 0.0: # Windows leaves this as zero, so use time.clock()
715 t = (t[0], t[1], t[2], t[3], time.clock())
746 t = (t[0], t[1], t[2], t[3], time.clock())
716 return t
747 return t
717 s = get_times()
748 s = get_times()
718 def print_time():
749 def print_time():
719 t = get_times()
750 t = get_times()
720 ui.warn(_("time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
751 ui.warn(_("time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
721 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
752 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
722 atexit.register(print_time)
753 atexit.register(print_time)
723
754
724 uis = set([ui, lui])
755 uis = set([ui, lui])
725
756
726 if req.repo:
757 if req.repo:
727 uis.add(req.repo.ui)
758 uis.add(req.repo.ui)
728
759
729 if options['verbose'] or options['debug'] or options['quiet']:
760 if options['verbose'] or options['debug'] or options['quiet']:
730 for opt in ('verbose', 'debug', 'quiet'):
761 for opt in ('verbose', 'debug', 'quiet'):
731 val = str(bool(options[opt]))
762 val = str(bool(options[opt]))
732 for ui_ in uis:
763 for ui_ in uis:
733 ui_.setconfig('ui', opt, val, '--' + opt)
764 ui_.setconfig('ui', opt, val, '--' + opt)
734
765
735 if options['profile']:
766 if options['profile']:
736 for ui_ in uis:
767 for ui_ in uis:
737 ui_.setconfig('profiling', 'enabled', 'true', '--profile')
768 ui_.setconfig('profiling', 'enabled', 'true', '--profile')
738
769
739 if options['traceback']:
770 if options['traceback']:
740 for ui_ in uis:
771 for ui_ in uis:
741 ui_.setconfig('ui', 'traceback', 'on', '--traceback')
772 ui_.setconfig('ui', 'traceback', 'on', '--traceback')
742
773
743 if options['noninteractive']:
774 if options['noninteractive']:
744 for ui_ in uis:
775 for ui_ in uis:
745 ui_.setconfig('ui', 'interactive', 'off', '-y')
776 ui_.setconfig('ui', 'interactive', 'off', '-y')
746
777
747 if cmdoptions.get('insecure', False):
778 if cmdoptions.get('insecure', False):
748 for ui_ in uis:
779 for ui_ in uis:
749 ui_.insecureconnections = True
780 ui_.insecureconnections = True
750
781
751 if options['version']:
782 if options['version']:
752 return commands.version_(ui)
783 return commands.version_(ui)
753 if options['help']:
784 if options['help']:
754 return commands.help_(ui, cmd, command=cmd is not None)
785 return commands.help_(ui, cmd, command=cmd is not None)
755 elif not cmd:
786 elif not cmd:
756 return commands.help_(ui, 'shortlist')
787 return commands.help_(ui, 'shortlist')
757
788
758 with profiling.maybeprofile(lui):
789 with profiling.maybeprofile(lui):
759 repo = None
790 repo = None
760 cmdpats = args[:]
791 cmdpats = args[:]
761 if not func.norepo:
792 if not func.norepo:
762 # use the repo from the request only if we don't have -R
793 # use the repo from the request only if we don't have -R
763 if not rpath and not cwd:
794 if not rpath and not cwd:
764 repo = req.repo
795 repo = req.repo
765
796
766 if repo:
797 if repo:
767 # set the descriptors of the repo ui to those of ui
798 # set the descriptors of the repo ui to those of ui
768 repo.ui.fin = ui.fin
799 repo.ui.fin = ui.fin
769 repo.ui.fout = ui.fout
800 repo.ui.fout = ui.fout
770 repo.ui.ferr = ui.ferr
801 repo.ui.ferr = ui.ferr
771 else:
802 else:
772 try:
803 try:
773 repo = hg.repository(ui, path=path)
804 repo = hg.repository(ui, path=path)
774 if not repo.local():
805 if not repo.local():
775 raise error.Abort(_("repository '%s' is not local")
806 raise error.Abort(_("repository '%s' is not local")
776 % path)
807 % path)
777 repo.ui.setconfig("bundle", "mainreporoot", repo.root,
808 repo.ui.setconfig("bundle", "mainreporoot", repo.root,
778 'repo')
809 'repo')
779 except error.RequirementError:
810 except error.RequirementError:
780 raise
811 raise
781 except error.RepoError:
812 except error.RepoError:
782 if rpath and rpath[-1]: # invalid -R path
813 if rpath and rpath[-1]: # invalid -R path
783 raise
814 raise
784 if not func.optionalrepo:
815 if not func.optionalrepo:
785 if func.inferrepo and args and not path:
816 if func.inferrepo and args and not path:
786 # try to infer -R from command args
817 # try to infer -R from command args
787 repos = map(cmdutil.findrepo, args)
818 repos = map(cmdutil.findrepo, args)
788 guess = repos[0]
819 guess = repos[0]
789 if guess and repos.count(guess) == len(repos):
820 if guess and repos.count(guess) == len(repos):
790 req.args = ['--repository', guess] + fullargs
821 req.args = ['--repository', guess] + fullargs
791 return _dispatch(req)
822 return _dispatch(req)
792 if not path:
823 if not path:
793 raise error.RepoError(_("no repository found in"
824 raise error.RepoError(_("no repository found in"
794 " '%s' (.hg not found)")
825 " '%s' (.hg not found)")
795 % pycompat.getcwd())
826 % pycompat.getcwd())
796 raise
827 raise
797 if repo:
828 if repo:
798 ui = repo.ui
829 ui = repo.ui
799 if options['hidden']:
830 if options['hidden']:
800 repo = repo.unfiltered()
831 repo = repo.unfiltered()
801 args.insert(0, repo)
832 args.insert(0, repo)
802 elif rpath:
833 elif rpath:
803 ui.warn(_("warning: --repository ignored\n"))
834 ui.warn(_("warning: --repository ignored\n"))
804
835
805 msg = ' '.join(' ' in a and repr(a) or a for a in fullargs)
836 msg = ' '.join(' ' in a and repr(a) or a for a in fullargs)
806 ui.log("command", '%s\n', msg)
837 ui.log("command", '%s\n', msg)
807 strcmdopt = pycompat.strkwargs(cmdoptions)
838 strcmdopt = pycompat.strkwargs(cmdoptions)
808 d = lambda: util.checksignature(func)(ui, *args, **strcmdopt)
839 d = lambda: util.checksignature(func)(ui, *args, **strcmdopt)
809 try:
840 try:
810 return runcommand(lui, repo, cmd, fullargs, ui, options, d,
841 return runcommand(lui, repo, cmd, fullargs, ui, options, d,
811 cmdpats, cmdoptions)
842 cmdpats, cmdoptions)
812 finally:
843 finally:
813 if repo and repo != req.repo:
844 if repo and repo != req.repo:
814 repo.close()
845 repo.close()
815
846
816 def _runcommand(ui, options, cmd, cmdfunc):
847 def _runcommand(ui, options, cmd, cmdfunc):
817 """Run a command function, possibly with profiling enabled."""
848 """Run a command function, possibly with profiling enabled."""
818 try:
849 try:
819 return cmdfunc()
850 return cmdfunc()
820 except error.SignatureError:
851 except error.SignatureError:
821 raise error.CommandError(cmd, _('invalid arguments'))
852 raise error.CommandError(cmd, _('invalid arguments'))
822
853
823 def _exceptionwarning(ui):
854 def _exceptionwarning(ui):
824 """Produce a warning message for the current active exception"""
855 """Produce a warning message for the current active exception"""
825
856
826 # For compatibility checking, we discard the portion of the hg
857 # For compatibility checking, we discard the portion of the hg
827 # version after the + on the assumption that if a "normal
858 # version after the + on the assumption that if a "normal
828 # user" is running a build with a + in it the packager
859 # user" is running a build with a + in it the packager
829 # probably built from fairly close to a tag and anyone with a
860 # probably built from fairly close to a tag and anyone with a
830 # 'make local' copy of hg (where the version number can be out
861 # 'make local' copy of hg (where the version number can be out
831 # of date) will be clueful enough to notice the implausible
862 # of date) will be clueful enough to notice the implausible
832 # version number and try updating.
863 # version number and try updating.
833 ct = util.versiontuple(n=2)
864 ct = util.versiontuple(n=2)
834 worst = None, ct, ''
865 worst = None, ct, ''
835 if ui.config('ui', 'supportcontact', None) is None:
866 if ui.config('ui', 'supportcontact', None) is None:
836 for name, mod in extensions.extensions():
867 for name, mod in extensions.extensions():
837 testedwith = getattr(mod, 'testedwith', '')
868 testedwith = getattr(mod, 'testedwith', '')
838 report = getattr(mod, 'buglink', _('the extension author.'))
869 report = getattr(mod, 'buglink', _('the extension author.'))
839 if not testedwith.strip():
870 if not testedwith.strip():
840 # We found an untested extension. It's likely the culprit.
871 # We found an untested extension. It's likely the culprit.
841 worst = name, 'unknown', report
872 worst = name, 'unknown', report
842 break
873 break
843
874
844 # Never blame on extensions bundled with Mercurial.
875 # Never blame on extensions bundled with Mercurial.
845 if extensions.ismoduleinternal(mod):
876 if extensions.ismoduleinternal(mod):
846 continue
877 continue
847
878
848 tested = [util.versiontuple(t, 2) for t in testedwith.split()]
879 tested = [util.versiontuple(t, 2) for t in testedwith.split()]
849 if ct in tested:
880 if ct in tested:
850 continue
881 continue
851
882
852 lower = [t for t in tested if t < ct]
883 lower = [t for t in tested if t < ct]
853 nearest = max(lower or tested)
884 nearest = max(lower or tested)
854 if worst[0] is None or nearest < worst[1]:
885 if worst[0] is None or nearest < worst[1]:
855 worst = name, nearest, report
886 worst = name, nearest, report
856 if worst[0] is not None:
887 if worst[0] is not None:
857 name, testedwith, report = worst
888 name, testedwith, report = worst
858 if not isinstance(testedwith, str):
889 if not isinstance(testedwith, str):
859 testedwith = '.'.join([str(c) for c in testedwith])
890 testedwith = '.'.join([str(c) for c in testedwith])
860 warning = (_('** Unknown exception encountered with '
891 warning = (_('** Unknown exception encountered with '
861 'possibly-broken third-party extension %s\n'
892 'possibly-broken third-party extension %s\n'
862 '** which supports versions %s of Mercurial.\n'
893 '** which supports versions %s of Mercurial.\n'
863 '** Please disable %s and try your action again.\n'
894 '** Please disable %s and try your action again.\n'
864 '** If that fixes the bug please report it to %s\n')
895 '** If that fixes the bug please report it to %s\n')
865 % (name, testedwith, name, report))
896 % (name, testedwith, name, report))
866 else:
897 else:
867 bugtracker = ui.config('ui', 'supportcontact', None)
898 bugtracker = ui.config('ui', 'supportcontact', None)
868 if bugtracker is None:
899 if bugtracker is None:
869 bugtracker = _("https://mercurial-scm.org/wiki/BugTracker")
900 bugtracker = _("https://mercurial-scm.org/wiki/BugTracker")
870 warning = (_("** unknown exception encountered, "
901 warning = (_("** unknown exception encountered, "
871 "please report by visiting\n** ") + bugtracker + '\n')
902 "please report by visiting\n** ") + bugtracker + '\n')
872 warning += ((_("** Python %s\n") % sys.version.replace('\n', '')) +
903 warning += ((_("** Python %s\n") % sys.version.replace('\n', '')) +
873 (_("** Mercurial Distributed SCM (version %s)\n") %
904 (_("** Mercurial Distributed SCM (version %s)\n") %
874 util.version()) +
905 util.version()) +
875 (_("** Extensions loaded: %s\n") %
906 (_("** Extensions loaded: %s\n") %
876 ", ".join([x[0] for x in extensions.extensions()])))
907 ", ".join([x[0] for x in extensions.extensions()])))
877 return warning
908 return warning
878
909
879 def handlecommandexception(ui):
910 def handlecommandexception(ui):
880 """Produce a warning message for broken commands
911 """Produce a warning message for broken commands
881
912
882 Called when handling an exception; the exception is reraised if
913 Called when handling an exception; the exception is reraised if
883 this function returns False, ignored otherwise.
914 this function returns False, ignored otherwise.
884 """
915 """
885 warning = _exceptionwarning(ui)
916 warning = _exceptionwarning(ui)
886 ui.log("commandexception", "%s\n%s\n", warning, traceback.format_exc())
917 ui.log("commandexception", "%s\n%s\n", warning, traceback.format_exc())
887 ui.warn(warning)
918 ui.warn(warning)
888 return False # re-raise the exception
919 return False # re-raise the exception
@@ -1,564 +1,577 b''
1
1
2 This test tries to exercise the ssh functionality with a dummy script
2 This test tries to exercise the ssh functionality with a dummy script
3
3
4 $ cat <<EOF >> $HGRCPATH
4 $ cat <<EOF >> $HGRCPATH
5 > [format]
5 > [format]
6 > usegeneraldelta=yes
6 > usegeneraldelta=yes
7 > EOF
7 > EOF
8
8
9 creating 'remote' repo
9 creating 'remote' repo
10
10
11 $ hg init remote
11 $ hg init remote
12 $ cd remote
12 $ cd remote
13 $ echo this > foo
13 $ echo this > foo
14 $ echo this > fooO
14 $ echo this > fooO
15 $ hg ci -A -m "init" foo fooO
15 $ hg ci -A -m "init" foo fooO
16
16
17 insert a closed branch (issue4428)
17 insert a closed branch (issue4428)
18
18
19 $ hg up null
19 $ hg up null
20 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
20 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
21 $ hg branch closed
21 $ hg branch closed
22 marked working directory as branch closed
22 marked working directory as branch closed
23 (branches are permanent and global, did you want a bookmark?)
23 (branches are permanent and global, did you want a bookmark?)
24 $ hg ci -mc0
24 $ hg ci -mc0
25 $ hg ci --close-branch -mc1
25 $ hg ci --close-branch -mc1
26 $ hg up -q default
26 $ hg up -q default
27
27
28 configure for serving
28 configure for serving
29
29
30 $ cat <<EOF > .hg/hgrc
30 $ cat <<EOF > .hg/hgrc
31 > [server]
31 > [server]
32 > uncompressed = True
32 > uncompressed = True
33 >
33 >
34 > [hooks]
34 > [hooks]
35 > changegroup = sh -c "printenv.py changegroup-in-remote 0 ../dummylog"
35 > changegroup = sh -c "printenv.py changegroup-in-remote 0 ../dummylog"
36 > EOF
36 > EOF
37 $ cd ..
37 $ cd ..
38
38
39 repo not found error
39 repo not found error
40
40
41 $ hg clone -e "python \"$TESTDIR/dummyssh\"" ssh://user@dummy/nonexistent local
41 $ hg clone -e "python \"$TESTDIR/dummyssh\"" ssh://user@dummy/nonexistent local
42 remote: abort: repository nonexistent not found!
42 remote: abort: repository nonexistent not found!
43 abort: no suitable response from remote hg!
43 abort: no suitable response from remote hg!
44 [255]
44 [255]
45
45
46 non-existent absolute path
46 non-existent absolute path
47
47
48 $ hg clone -e "python \"$TESTDIR/dummyssh\"" ssh://user@dummy/`pwd`/nonexistent local
48 $ hg clone -e "python \"$TESTDIR/dummyssh\"" ssh://user@dummy/`pwd`/nonexistent local
49 remote: abort: repository $TESTTMP/nonexistent not found!
49 remote: abort: repository $TESTTMP/nonexistent not found!
50 abort: no suitable response from remote hg!
50 abort: no suitable response from remote hg!
51 [255]
51 [255]
52
52
53 clone remote via stream
53 clone remote via stream
54
54
55 $ hg clone -e "python \"$TESTDIR/dummyssh\"" --uncompressed ssh://user@dummy/remote local-stream
55 $ hg clone -e "python \"$TESTDIR/dummyssh\"" --uncompressed ssh://user@dummy/remote local-stream
56 streaming all changes
56 streaming all changes
57 4 files to transfer, 602 bytes of data
57 4 files to transfer, 602 bytes of data
58 transferred 602 bytes in * seconds (*) (glob)
58 transferred 602 bytes in * seconds (*) (glob)
59 searching for changes
59 searching for changes
60 no changes found
60 no changes found
61 updating to branch default
61 updating to branch default
62 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
62 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
63 $ cd local-stream
63 $ cd local-stream
64 $ hg verify
64 $ hg verify
65 checking changesets
65 checking changesets
66 checking manifests
66 checking manifests
67 crosschecking files in changesets and manifests
67 crosschecking files in changesets and manifests
68 checking files
68 checking files
69 2 files, 3 changesets, 2 total revisions
69 2 files, 3 changesets, 2 total revisions
70 $ hg branches
70 $ hg branches
71 default 0:1160648e36ce
71 default 0:1160648e36ce
72 $ cd ..
72 $ cd ..
73
73
74 clone bookmarks via stream
74 clone bookmarks via stream
75
75
76 $ hg -R local-stream book mybook
76 $ hg -R local-stream book mybook
77 $ hg clone -e "python \"$TESTDIR/dummyssh\"" --uncompressed ssh://user@dummy/local-stream stream2
77 $ hg clone -e "python \"$TESTDIR/dummyssh\"" --uncompressed ssh://user@dummy/local-stream stream2
78 streaming all changes
78 streaming all changes
79 4 files to transfer, 602 bytes of data
79 4 files to transfer, 602 bytes of data
80 transferred 602 bytes in * seconds (*) (glob)
80 transferred 602 bytes in * seconds (*) (glob)
81 searching for changes
81 searching for changes
82 no changes found
82 no changes found
83 updating to branch default
83 updating to branch default
84 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
84 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
85 $ cd stream2
85 $ cd stream2
86 $ hg book
86 $ hg book
87 mybook 0:1160648e36ce
87 mybook 0:1160648e36ce
88 $ cd ..
88 $ cd ..
89 $ rm -rf local-stream stream2
89 $ rm -rf local-stream stream2
90
90
91 clone remote via pull
91 clone remote via pull
92
92
93 $ hg clone -e "python \"$TESTDIR/dummyssh\"" ssh://user@dummy/remote local
93 $ hg clone -e "python \"$TESTDIR/dummyssh\"" ssh://user@dummy/remote local
94 requesting all changes
94 requesting all changes
95 adding changesets
95 adding changesets
96 adding manifests
96 adding manifests
97 adding file changes
97 adding file changes
98 added 3 changesets with 2 changes to 2 files
98 added 3 changesets with 2 changes to 2 files
99 updating to branch default
99 updating to branch default
100 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
100 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
101
101
102 verify
102 verify
103
103
104 $ cd local
104 $ cd local
105 $ hg verify
105 $ hg verify
106 checking changesets
106 checking changesets
107 checking manifests
107 checking manifests
108 crosschecking files in changesets and manifests
108 crosschecking files in changesets and manifests
109 checking files
109 checking files
110 2 files, 3 changesets, 2 total revisions
110 2 files, 3 changesets, 2 total revisions
111 $ cat >> .hg/hgrc <<EOF
111 $ cat >> .hg/hgrc <<EOF
112 > [hooks]
112 > [hooks]
113 > changegroup = sh -c "printenv.py changegroup-in-local 0 ../dummylog"
113 > changegroup = sh -c "printenv.py changegroup-in-local 0 ../dummylog"
114 > EOF
114 > EOF
115
115
116 empty default pull
116 empty default pull
117
117
118 $ hg paths
118 $ hg paths
119 default = ssh://user@dummy/remote
119 default = ssh://user@dummy/remote
120 $ hg pull -e "python \"$TESTDIR/dummyssh\""
120 $ hg pull -e "python \"$TESTDIR/dummyssh\""
121 pulling from ssh://user@dummy/remote
121 pulling from ssh://user@dummy/remote
122 searching for changes
122 searching for changes
123 no changes found
123 no changes found
124
124
125 pull from wrong ssh URL
125 pull from wrong ssh URL
126
126
127 $ hg pull -e "python \"$TESTDIR/dummyssh\"" ssh://user@dummy/doesnotexist
127 $ hg pull -e "python \"$TESTDIR/dummyssh\"" ssh://user@dummy/doesnotexist
128 pulling from ssh://user@dummy/doesnotexist
128 pulling from ssh://user@dummy/doesnotexist
129 remote: abort: repository doesnotexist not found!
129 remote: abort: repository doesnotexist not found!
130 abort: no suitable response from remote hg!
130 abort: no suitable response from remote hg!
131 [255]
131 [255]
132
132
133 local change
133 local change
134
134
135 $ echo bleah > foo
135 $ echo bleah > foo
136 $ hg ci -m "add"
136 $ hg ci -m "add"
137
137
138 updating rc
138 updating rc
139
139
140 $ echo "default-push = ssh://user@dummy/remote" >> .hg/hgrc
140 $ echo "default-push = ssh://user@dummy/remote" >> .hg/hgrc
141 $ echo "[ui]" >> .hg/hgrc
141 $ echo "[ui]" >> .hg/hgrc
142 $ echo "ssh = python \"$TESTDIR/dummyssh\"" >> .hg/hgrc
142 $ echo "ssh = python \"$TESTDIR/dummyssh\"" >> .hg/hgrc
143
143
144 find outgoing
144 find outgoing
145
145
146 $ hg out ssh://user@dummy/remote
146 $ hg out ssh://user@dummy/remote
147 comparing with ssh://user@dummy/remote
147 comparing with ssh://user@dummy/remote
148 searching for changes
148 searching for changes
149 changeset: 3:a28a9d1a809c
149 changeset: 3:a28a9d1a809c
150 tag: tip
150 tag: tip
151 parent: 0:1160648e36ce
151 parent: 0:1160648e36ce
152 user: test
152 user: test
153 date: Thu Jan 01 00:00:00 1970 +0000
153 date: Thu Jan 01 00:00:00 1970 +0000
154 summary: add
154 summary: add
155
155
156
156
157 find incoming on the remote side
157 find incoming on the remote side
158
158
159 $ hg incoming -R ../remote -e "python \"$TESTDIR/dummyssh\"" ssh://user@dummy/local
159 $ hg incoming -R ../remote -e "python \"$TESTDIR/dummyssh\"" ssh://user@dummy/local
160 comparing with ssh://user@dummy/local
160 comparing with ssh://user@dummy/local
161 searching for changes
161 searching for changes
162 changeset: 3:a28a9d1a809c
162 changeset: 3:a28a9d1a809c
163 tag: tip
163 tag: tip
164 parent: 0:1160648e36ce
164 parent: 0:1160648e36ce
165 user: test
165 user: test
166 date: Thu Jan 01 00:00:00 1970 +0000
166 date: Thu Jan 01 00:00:00 1970 +0000
167 summary: add
167 summary: add
168
168
169
169
170 find incoming on the remote side (using absolute path)
170 find incoming on the remote side (using absolute path)
171
171
172 $ hg incoming -R ../remote -e "python \"$TESTDIR/dummyssh\"" "ssh://user@dummy/`pwd`"
172 $ hg incoming -R ../remote -e "python \"$TESTDIR/dummyssh\"" "ssh://user@dummy/`pwd`"
173 comparing with ssh://user@dummy/$TESTTMP/local
173 comparing with ssh://user@dummy/$TESTTMP/local
174 searching for changes
174 searching for changes
175 changeset: 3:a28a9d1a809c
175 changeset: 3:a28a9d1a809c
176 tag: tip
176 tag: tip
177 parent: 0:1160648e36ce
177 parent: 0:1160648e36ce
178 user: test
178 user: test
179 date: Thu Jan 01 00:00:00 1970 +0000
179 date: Thu Jan 01 00:00:00 1970 +0000
180 summary: add
180 summary: add
181
181
182
182
183 push
183 push
184
184
185 $ hg push
185 $ hg push
186 pushing to ssh://user@dummy/remote
186 pushing to ssh://user@dummy/remote
187 searching for changes
187 searching for changes
188 remote: adding changesets
188 remote: adding changesets
189 remote: adding manifests
189 remote: adding manifests
190 remote: adding file changes
190 remote: adding file changes
191 remote: added 1 changesets with 1 changes to 1 files
191 remote: added 1 changesets with 1 changes to 1 files
192 $ cd ../remote
192 $ cd ../remote
193
193
194 check remote tip
194 check remote tip
195
195
196 $ hg tip
196 $ hg tip
197 changeset: 3:a28a9d1a809c
197 changeset: 3:a28a9d1a809c
198 tag: tip
198 tag: tip
199 parent: 0:1160648e36ce
199 parent: 0:1160648e36ce
200 user: test
200 user: test
201 date: Thu Jan 01 00:00:00 1970 +0000
201 date: Thu Jan 01 00:00:00 1970 +0000
202 summary: add
202 summary: add
203
203
204 $ hg verify
204 $ hg verify
205 checking changesets
205 checking changesets
206 checking manifests
206 checking manifests
207 crosschecking files in changesets and manifests
207 crosschecking files in changesets and manifests
208 checking files
208 checking files
209 2 files, 4 changesets, 3 total revisions
209 2 files, 4 changesets, 3 total revisions
210 $ hg cat -r tip foo
210 $ hg cat -r tip foo
211 bleah
211 bleah
212 $ echo z > z
212 $ echo z > z
213 $ hg ci -A -m z z
213 $ hg ci -A -m z z
214 created new head
214 created new head
215
215
216 test pushkeys and bookmarks
216 test pushkeys and bookmarks
217
217
218 $ cd ../local
218 $ cd ../local
219 $ hg debugpushkey --config ui.ssh="python \"$TESTDIR/dummyssh\"" ssh://user@dummy/remote namespaces
219 $ hg debugpushkey --config ui.ssh="python \"$TESTDIR/dummyssh\"" ssh://user@dummy/remote namespaces
220 bookmarks
220 bookmarks
221 namespaces
221 namespaces
222 phases
222 phases
223 $ hg book foo -r 0
223 $ hg book foo -r 0
224 $ hg out -B
224 $ hg out -B
225 comparing with ssh://user@dummy/remote
225 comparing with ssh://user@dummy/remote
226 searching for changed bookmarks
226 searching for changed bookmarks
227 foo 1160648e36ce
227 foo 1160648e36ce
228 $ hg push -B foo
228 $ hg push -B foo
229 pushing to ssh://user@dummy/remote
229 pushing to ssh://user@dummy/remote
230 searching for changes
230 searching for changes
231 no changes found
231 no changes found
232 exporting bookmark foo
232 exporting bookmark foo
233 [1]
233 [1]
234 $ hg debugpushkey --config ui.ssh="python \"$TESTDIR/dummyssh\"" ssh://user@dummy/remote bookmarks
234 $ hg debugpushkey --config ui.ssh="python \"$TESTDIR/dummyssh\"" ssh://user@dummy/remote bookmarks
235 foo 1160648e36cec0054048a7edc4110c6f84fde594
235 foo 1160648e36cec0054048a7edc4110c6f84fde594
236 $ hg book -f foo
236 $ hg book -f foo
237 $ hg push --traceback
237 $ hg push --traceback
238 pushing to ssh://user@dummy/remote
238 pushing to ssh://user@dummy/remote
239 searching for changes
239 searching for changes
240 no changes found
240 no changes found
241 updating bookmark foo
241 updating bookmark foo
242 [1]
242 [1]
243 $ hg book -d foo
243 $ hg book -d foo
244 $ hg in -B
244 $ hg in -B
245 comparing with ssh://user@dummy/remote
245 comparing with ssh://user@dummy/remote
246 searching for changed bookmarks
246 searching for changed bookmarks
247 foo a28a9d1a809c
247 foo a28a9d1a809c
248 $ hg book -f -r 0 foo
248 $ hg book -f -r 0 foo
249 $ hg pull -B foo
249 $ hg pull -B foo
250 pulling from ssh://user@dummy/remote
250 pulling from ssh://user@dummy/remote
251 no changes found
251 no changes found
252 updating bookmark foo
252 updating bookmark foo
253 $ hg book -d foo
253 $ hg book -d foo
254 $ hg push -B foo
254 $ hg push -B foo
255 pushing to ssh://user@dummy/remote
255 pushing to ssh://user@dummy/remote
256 searching for changes
256 searching for changes
257 no changes found
257 no changes found
258 deleting remote bookmark foo
258 deleting remote bookmark foo
259 [1]
259 [1]
260
260
261 a bad, evil hook that prints to stdout
261 a bad, evil hook that prints to stdout
262
262
263 $ cat <<EOF > $TESTTMP/badhook
263 $ cat <<EOF > $TESTTMP/badhook
264 > import sys
264 > import sys
265 > sys.stdout.write("KABOOM\n")
265 > sys.stdout.write("KABOOM\n")
266 > EOF
266 > EOF
267
267
268 $ cat <<EOF > $TESTTMP/badpyhook.py
268 $ cat <<EOF > $TESTTMP/badpyhook.py
269 > import sys
269 > import sys
270 > def hook(ui, repo, hooktype, **kwargs):
270 > def hook(ui, repo, hooktype, **kwargs):
271 > sys.stdout.write("KABOOM IN PROCESS\n")
271 > sys.stdout.write("KABOOM IN PROCESS\n")
272 > EOF
272 > EOF
273
273
274 $ cat <<EOF >> ../remote/.hg/hgrc
274 $ cat <<EOF >> ../remote/.hg/hgrc
275 > [hooks]
275 > [hooks]
276 > changegroup.stdout = python $TESTTMP/badhook
276 > changegroup.stdout = python $TESTTMP/badhook
277 > changegroup.pystdout = python:$TESTTMP/badpyhook.py:hook
277 > changegroup.pystdout = python:$TESTTMP/badpyhook.py:hook
278 > EOF
278 > EOF
279 $ echo r > r
279 $ echo r > r
280 $ hg ci -A -m z r
280 $ hg ci -A -m z r
281
281
282 push should succeed even though it has an unexpected response
282 push should succeed even though it has an unexpected response
283
283
284 $ hg push
284 $ hg push
285 pushing to ssh://user@dummy/remote
285 pushing to ssh://user@dummy/remote
286 searching for changes
286 searching for changes
287 remote has heads on branch 'default' that are not known locally: 6c0482d977a3
287 remote has heads on branch 'default' that are not known locally: 6c0482d977a3
288 remote: adding changesets
288 remote: adding changesets
289 remote: adding manifests
289 remote: adding manifests
290 remote: adding file changes
290 remote: adding file changes
291 remote: added 1 changesets with 1 changes to 1 files
291 remote: added 1 changesets with 1 changes to 1 files
292 remote: KABOOM
292 remote: KABOOM
293 remote: KABOOM IN PROCESS
293 remote: KABOOM IN PROCESS
294 $ hg -R ../remote heads
294 $ hg -R ../remote heads
295 changeset: 5:1383141674ec
295 changeset: 5:1383141674ec
296 tag: tip
296 tag: tip
297 parent: 3:a28a9d1a809c
297 parent: 3:a28a9d1a809c
298 user: test
298 user: test
299 date: Thu Jan 01 00:00:00 1970 +0000
299 date: Thu Jan 01 00:00:00 1970 +0000
300 summary: z
300 summary: z
301
301
302 changeset: 4:6c0482d977a3
302 changeset: 4:6c0482d977a3
303 parent: 0:1160648e36ce
303 parent: 0:1160648e36ce
304 user: test
304 user: test
305 date: Thu Jan 01 00:00:00 1970 +0000
305 date: Thu Jan 01 00:00:00 1970 +0000
306 summary: z
306 summary: z
307
307
308
308
309 clone bookmarks
309 clone bookmarks
310
310
311 $ hg -R ../remote bookmark test
311 $ hg -R ../remote bookmark test
312 $ hg -R ../remote bookmarks
312 $ hg -R ../remote bookmarks
313 * test 4:6c0482d977a3
313 * test 4:6c0482d977a3
314 $ hg clone -e "python \"$TESTDIR/dummyssh\"" ssh://user@dummy/remote local-bookmarks
314 $ hg clone -e "python \"$TESTDIR/dummyssh\"" ssh://user@dummy/remote local-bookmarks
315 requesting all changes
315 requesting all changes
316 adding changesets
316 adding changesets
317 adding manifests
317 adding manifests
318 adding file changes
318 adding file changes
319 added 6 changesets with 5 changes to 4 files (+1 heads)
319 added 6 changesets with 5 changes to 4 files (+1 heads)
320 updating to branch default
320 updating to branch default
321 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
321 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
322 $ hg -R local-bookmarks bookmarks
322 $ hg -R local-bookmarks bookmarks
323 test 4:6c0482d977a3
323 test 4:6c0482d977a3
324
324
325 passwords in ssh urls are not supported
325 passwords in ssh urls are not supported
326 (we use a glob here because different Python versions give different
326 (we use a glob here because different Python versions give different
327 results here)
327 results here)
328
328
329 $ hg push ssh://user:erroneouspwd@dummy/remote
329 $ hg push ssh://user:erroneouspwd@dummy/remote
330 pushing to ssh://user:*@dummy/remote (glob)
330 pushing to ssh://user:*@dummy/remote (glob)
331 abort: password in URL not supported!
331 abort: password in URL not supported!
332 [255]
332 [255]
333
333
334 $ cd ..
334 $ cd ..
335
335
336 hide outer repo
336 hide outer repo
337 $ hg init
337 $ hg init
338
338
339 Test remote paths with spaces (issue2983):
339 Test remote paths with spaces (issue2983):
340
340
341 $ hg init --ssh "python \"$TESTDIR/dummyssh\"" "ssh://user@dummy/a repo"
341 $ hg init --ssh "python \"$TESTDIR/dummyssh\"" "ssh://user@dummy/a repo"
342 $ touch "$TESTTMP/a repo/test"
342 $ touch "$TESTTMP/a repo/test"
343 $ hg -R 'a repo' commit -A -m "test"
343 $ hg -R 'a repo' commit -A -m "test"
344 adding test
344 adding test
345 $ hg -R 'a repo' tag tag
345 $ hg -R 'a repo' tag tag
346 $ hg id --ssh "python \"$TESTDIR/dummyssh\"" "ssh://user@dummy/a repo"
346 $ hg id --ssh "python \"$TESTDIR/dummyssh\"" "ssh://user@dummy/a repo"
347 73649e48688a
347 73649e48688a
348
348
349 $ hg id --ssh "python \"$TESTDIR/dummyssh\"" "ssh://user@dummy/a repo#noNoNO"
349 $ hg id --ssh "python \"$TESTDIR/dummyssh\"" "ssh://user@dummy/a repo#noNoNO"
350 abort: unknown revision 'noNoNO'!
350 abort: unknown revision 'noNoNO'!
351 [255]
351 [255]
352
352
353 Test (non-)escaping of remote paths with spaces when cloning (issue3145):
353 Test (non-)escaping of remote paths with spaces when cloning (issue3145):
354
354
355 $ hg clone --ssh "python \"$TESTDIR/dummyssh\"" "ssh://user@dummy/a repo"
355 $ hg clone --ssh "python \"$TESTDIR/dummyssh\"" "ssh://user@dummy/a repo"
356 destination directory: a repo
356 destination directory: a repo
357 abort: destination 'a repo' is not empty
357 abort: destination 'a repo' is not empty
358 [255]
358 [255]
359
359
360 Make sure hg is really paranoid in serve --stdio mode. It used to be
361 possible to get a debugger REPL by specifying a repo named --debugger.
362 $ hg -R --debugger serve --stdio
363 abort: potentially unsafe serve --stdio invocation: ['-R', '--debugger', 'serve', '--stdio']
364 [255]
365 $ hg -R --config=ui.debugger=yes serve --stdio
366 abort: potentially unsafe serve --stdio invocation: ['-R', '--config=ui.debugger=yes', 'serve', '--stdio']
367 [255]
368 Abbreviations of 'serve' also don't work, to avoid shenanigans.
369 $ hg -R narf serv --stdio
370 abort: potentially unsafe serve --stdio invocation: ['-R', 'narf', 'serv', '--stdio']
371 [255]
372
360 Test hg-ssh using a helper script that will restore PYTHONPATH (which might
373 Test hg-ssh using a helper script that will restore PYTHONPATH (which might
361 have been cleared by a hg.exe wrapper) and invoke hg-ssh with the right
374 have been cleared by a hg.exe wrapper) and invoke hg-ssh with the right
362 parameters:
375 parameters:
363
376
364 $ cat > ssh.sh << EOF
377 $ cat > ssh.sh << EOF
365 > userhost="\$1"
378 > userhost="\$1"
366 > SSH_ORIGINAL_COMMAND="\$2"
379 > SSH_ORIGINAL_COMMAND="\$2"
367 > export SSH_ORIGINAL_COMMAND
380 > export SSH_ORIGINAL_COMMAND
368 > PYTHONPATH="$PYTHONPATH"
381 > PYTHONPATH="$PYTHONPATH"
369 > export PYTHONPATH
382 > export PYTHONPATH
370 > python "$TESTDIR/../contrib/hg-ssh" "$TESTTMP/a repo"
383 > python "$TESTDIR/../contrib/hg-ssh" "$TESTTMP/a repo"
371 > EOF
384 > EOF
372
385
373 $ hg id --ssh "sh ssh.sh" "ssh://user@dummy/a repo"
386 $ hg id --ssh "sh ssh.sh" "ssh://user@dummy/a repo"
374 73649e48688a
387 73649e48688a
375
388
376 $ hg id --ssh "sh ssh.sh" "ssh://user@dummy/a'repo"
389 $ hg id --ssh "sh ssh.sh" "ssh://user@dummy/a'repo"
377 remote: Illegal repository "$TESTTMP/a'repo" (glob)
390 remote: Illegal repository "$TESTTMP/a'repo" (glob)
378 abort: no suitable response from remote hg!
391 abort: no suitable response from remote hg!
379 [255]
392 [255]
380
393
381 $ hg id --ssh "sh ssh.sh" --remotecmd hacking "ssh://user@dummy/a'repo"
394 $ hg id --ssh "sh ssh.sh" --remotecmd hacking "ssh://user@dummy/a'repo"
382 remote: Illegal command "hacking -R 'a'\''repo' serve --stdio"
395 remote: Illegal command "hacking -R 'a'\''repo' serve --stdio"
383 abort: no suitable response from remote hg!
396 abort: no suitable response from remote hg!
384 [255]
397 [255]
385
398
386 $ SSH_ORIGINAL_COMMAND="'hg' -R 'a'repo' serve --stdio" python "$TESTDIR/../contrib/hg-ssh"
399 $ SSH_ORIGINAL_COMMAND="'hg' -R 'a'repo' serve --stdio" python "$TESTDIR/../contrib/hg-ssh"
387 Illegal command "'hg' -R 'a'repo' serve --stdio": No closing quotation
400 Illegal command "'hg' -R 'a'repo' serve --stdio": No closing quotation
388 [255]
401 [255]
389
402
390 Test hg-ssh in read-only mode:
403 Test hg-ssh in read-only mode:
391
404
392 $ cat > ssh.sh << EOF
405 $ cat > ssh.sh << EOF
393 > userhost="\$1"
406 > userhost="\$1"
394 > SSH_ORIGINAL_COMMAND="\$2"
407 > SSH_ORIGINAL_COMMAND="\$2"
395 > export SSH_ORIGINAL_COMMAND
408 > export SSH_ORIGINAL_COMMAND
396 > PYTHONPATH="$PYTHONPATH"
409 > PYTHONPATH="$PYTHONPATH"
397 > export PYTHONPATH
410 > export PYTHONPATH
398 > python "$TESTDIR/../contrib/hg-ssh" --read-only "$TESTTMP/remote"
411 > python "$TESTDIR/../contrib/hg-ssh" --read-only "$TESTTMP/remote"
399 > EOF
412 > EOF
400
413
401 $ hg clone --ssh "sh ssh.sh" "ssh://user@dummy/$TESTTMP/remote" read-only-local
414 $ hg clone --ssh "sh ssh.sh" "ssh://user@dummy/$TESTTMP/remote" read-only-local
402 requesting all changes
415 requesting all changes
403 adding changesets
416 adding changesets
404 adding manifests
417 adding manifests
405 adding file changes
418 adding file changes
406 added 6 changesets with 5 changes to 4 files (+1 heads)
419 added 6 changesets with 5 changes to 4 files (+1 heads)
407 updating to branch default
420 updating to branch default
408 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
421 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
409
422
410 $ cd read-only-local
423 $ cd read-only-local
411 $ echo "baz" > bar
424 $ echo "baz" > bar
412 $ hg ci -A -m "unpushable commit" bar
425 $ hg ci -A -m "unpushable commit" bar
413 $ hg push --ssh "sh ../ssh.sh"
426 $ hg push --ssh "sh ../ssh.sh"
414 pushing to ssh://user@dummy/*/remote (glob)
427 pushing to ssh://user@dummy/*/remote (glob)
415 searching for changes
428 searching for changes
416 remote: Permission denied
429 remote: Permission denied
417 remote: pretxnopen.hg-ssh hook failed
430 remote: pretxnopen.hg-ssh hook failed
418 abort: push failed on remote
431 abort: push failed on remote
419 [255]
432 [255]
420
433
421 $ cd ..
434 $ cd ..
422
435
423 stderr from remote commands should be printed before stdout from local code (issue4336)
436 stderr from remote commands should be printed before stdout from local code (issue4336)
424
437
425 $ hg clone remote stderr-ordering
438 $ hg clone remote stderr-ordering
426 updating to branch default
439 updating to branch default
427 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
440 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
428 $ cd stderr-ordering
441 $ cd stderr-ordering
429 $ cat >> localwrite.py << EOF
442 $ cat >> localwrite.py << EOF
430 > from mercurial import exchange, extensions
443 > from mercurial import exchange, extensions
431 >
444 >
432 > def wrappedpush(orig, repo, *args, **kwargs):
445 > def wrappedpush(orig, repo, *args, **kwargs):
433 > res = orig(repo, *args, **kwargs)
446 > res = orig(repo, *args, **kwargs)
434 > repo.ui.write('local stdout\n')
447 > repo.ui.write('local stdout\n')
435 > return res
448 > return res
436 >
449 >
437 > def extsetup(ui):
450 > def extsetup(ui):
438 > extensions.wrapfunction(exchange, 'push', wrappedpush)
451 > extensions.wrapfunction(exchange, 'push', wrappedpush)
439 > EOF
452 > EOF
440
453
441 $ cat >> .hg/hgrc << EOF
454 $ cat >> .hg/hgrc << EOF
442 > [paths]
455 > [paths]
443 > default-push = ssh://user@dummy/remote
456 > default-push = ssh://user@dummy/remote
444 > [ui]
457 > [ui]
445 > ssh = python "$TESTDIR/dummyssh"
458 > ssh = python "$TESTDIR/dummyssh"
446 > [extensions]
459 > [extensions]
447 > localwrite = localwrite.py
460 > localwrite = localwrite.py
448 > EOF
461 > EOF
449
462
450 $ echo localwrite > foo
463 $ echo localwrite > foo
451 $ hg commit -m 'testing localwrite'
464 $ hg commit -m 'testing localwrite'
452 $ hg push
465 $ hg push
453 pushing to ssh://user@dummy/remote
466 pushing to ssh://user@dummy/remote
454 searching for changes
467 searching for changes
455 remote: adding changesets
468 remote: adding changesets
456 remote: adding manifests
469 remote: adding manifests
457 remote: adding file changes
470 remote: adding file changes
458 remote: added 1 changesets with 1 changes to 1 files
471 remote: added 1 changesets with 1 changes to 1 files
459 remote: KABOOM
472 remote: KABOOM
460 remote: KABOOM IN PROCESS
473 remote: KABOOM IN PROCESS
461 local stdout
474 local stdout
462
475
463 debug output
476 debug output
464
477
465 $ hg pull --debug ssh://user@dummy/remote
478 $ hg pull --debug ssh://user@dummy/remote
466 pulling from ssh://user@dummy/remote
479 pulling from ssh://user@dummy/remote
467 running python ".*/dummyssh" user@dummy ('|")hg -R remote serve --stdio('|") (re)
480 running python ".*/dummyssh" user@dummy ('|")hg -R remote serve --stdio('|") (re)
468 sending hello command
481 sending hello command
469 sending between command
482 sending between command
470 remote: 355
483 remote: 355
471 remote: capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512%0Aerror%3Dabort%2Cunsupportedcontent%2Cpushraced%2Cpushkey%0Ahgtagsfnodes%0Alistkeys%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps unbundle=HG10GZ,HG10BZ,HG10UN
484 remote: capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512%0Aerror%3Dabort%2Cunsupportedcontent%2Cpushraced%2Cpushkey%0Ahgtagsfnodes%0Alistkeys%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps unbundle=HG10GZ,HG10BZ,HG10UN
472 remote: 1
485 remote: 1
473 query 1; heads
486 query 1; heads
474 sending batch command
487 sending batch command
475 searching for changes
488 searching for changes
476 all remote heads known locally
489 all remote heads known locally
477 no changes found
490 no changes found
478 sending getbundle command
491 sending getbundle command
479 bundle2-input-bundle: with-transaction
492 bundle2-input-bundle: with-transaction
480 bundle2-input-part: "listkeys" (params: 1 mandatory) supported
493 bundle2-input-part: "listkeys" (params: 1 mandatory) supported
481 bundle2-input-part: total payload size 15
494 bundle2-input-part: total payload size 15
482 bundle2-input-part: "listkeys" (params: 1 mandatory) supported
495 bundle2-input-part: "listkeys" (params: 1 mandatory) supported
483 bundle2-input-part: total payload size 45
496 bundle2-input-part: total payload size 45
484 bundle2-input-bundle: 1 parts total
497 bundle2-input-bundle: 1 parts total
485 checking for updated bookmarks
498 checking for updated bookmarks
486
499
487 $ cd ..
500 $ cd ..
488
501
489 $ cat dummylog
502 $ cat dummylog
490 Got arguments 1:user@dummy 2:hg -R nonexistent serve --stdio
503 Got arguments 1:user@dummy 2:hg -R nonexistent serve --stdio
491 Got arguments 1:user@dummy 2:hg -R $TESTTMP/nonexistent serve --stdio
504 Got arguments 1:user@dummy 2:hg -R $TESTTMP/nonexistent serve --stdio
492 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
505 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
493 Got arguments 1:user@dummy 2:hg -R local-stream serve --stdio
506 Got arguments 1:user@dummy 2:hg -R local-stream serve --stdio
494 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
507 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
495 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
508 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
496 Got arguments 1:user@dummy 2:hg -R doesnotexist serve --stdio
509 Got arguments 1:user@dummy 2:hg -R doesnotexist serve --stdio
497 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
510 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
498 Got arguments 1:user@dummy 2:hg -R local serve --stdio
511 Got arguments 1:user@dummy 2:hg -R local serve --stdio
499 Got arguments 1:user@dummy 2:hg -R $TESTTMP/local serve --stdio
512 Got arguments 1:user@dummy 2:hg -R $TESTTMP/local serve --stdio
500 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
513 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
501 changegroup-in-remote hook: HG_BUNDLE2=1 HG_NODE=a28a9d1a809cab7d4e2fde4bee738a9ede948b60 HG_NODE_LAST=a28a9d1a809cab7d4e2fde4bee738a9ede948b60 HG_SOURCE=serve HG_TXNID=TXN:* HG_URL=remote:ssh:127.0.0.1 (glob)
514 changegroup-in-remote hook: HG_BUNDLE2=1 HG_NODE=a28a9d1a809cab7d4e2fde4bee738a9ede948b60 HG_NODE_LAST=a28a9d1a809cab7d4e2fde4bee738a9ede948b60 HG_SOURCE=serve HG_TXNID=TXN:* HG_URL=remote:ssh:127.0.0.1 (glob)
502 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
515 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
503 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
516 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
504 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
517 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
505 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
518 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
506 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
519 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
507 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
520 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
508 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
521 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
509 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
522 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
510 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
523 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
511 changegroup-in-remote hook: HG_BUNDLE2=1 HG_NODE=1383141674ec756a6056f6a9097618482fe0f4a6 HG_NODE_LAST=1383141674ec756a6056f6a9097618482fe0f4a6 HG_SOURCE=serve HG_TXNID=TXN:* HG_URL=remote:ssh:127.0.0.1 (glob)
524 changegroup-in-remote hook: HG_BUNDLE2=1 HG_NODE=1383141674ec756a6056f6a9097618482fe0f4a6 HG_NODE_LAST=1383141674ec756a6056f6a9097618482fe0f4a6 HG_SOURCE=serve HG_TXNID=TXN:* HG_URL=remote:ssh:127.0.0.1 (glob)
512 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
525 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
513 Got arguments 1:user@dummy 2:hg init 'a repo'
526 Got arguments 1:user@dummy 2:hg init 'a repo'
514 Got arguments 1:user@dummy 2:hg -R 'a repo' serve --stdio
527 Got arguments 1:user@dummy 2:hg -R 'a repo' serve --stdio
515 Got arguments 1:user@dummy 2:hg -R 'a repo' serve --stdio
528 Got arguments 1:user@dummy 2:hg -R 'a repo' serve --stdio
516 Got arguments 1:user@dummy 2:hg -R 'a repo' serve --stdio
529 Got arguments 1:user@dummy 2:hg -R 'a repo' serve --stdio
517 Got arguments 1:user@dummy 2:hg -R 'a repo' serve --stdio
530 Got arguments 1:user@dummy 2:hg -R 'a repo' serve --stdio
518 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
531 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
519 changegroup-in-remote hook: HG_BUNDLE2=1 HG_NODE=65c38f4125f9602c8db4af56530cc221d93b8ef8 HG_NODE_LAST=65c38f4125f9602c8db4af56530cc221d93b8ef8 HG_SOURCE=serve HG_TXNID=TXN:* HG_URL=remote:ssh:127.0.0.1 (glob)
532 changegroup-in-remote hook: HG_BUNDLE2=1 HG_NODE=65c38f4125f9602c8db4af56530cc221d93b8ef8 HG_NODE_LAST=65c38f4125f9602c8db4af56530cc221d93b8ef8 HG_SOURCE=serve HG_TXNID=TXN:* HG_URL=remote:ssh:127.0.0.1 (glob)
520 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
533 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
521
534
522 remote hook failure is attributed to remote
535 remote hook failure is attributed to remote
523
536
524 $ cat > $TESTTMP/failhook << EOF
537 $ cat > $TESTTMP/failhook << EOF
525 > def hook(ui, repo, **kwargs):
538 > def hook(ui, repo, **kwargs):
526 > ui.write('hook failure!\n')
539 > ui.write('hook failure!\n')
527 > ui.flush()
540 > ui.flush()
528 > return 1
541 > return 1
529 > EOF
542 > EOF
530
543
531 $ echo "pretxnchangegroup.fail = python:$TESTTMP/failhook:hook" >> remote/.hg/hgrc
544 $ echo "pretxnchangegroup.fail = python:$TESTTMP/failhook:hook" >> remote/.hg/hgrc
532
545
533 $ hg -q --config ui.ssh="python $TESTDIR/dummyssh" clone ssh://user@dummy/remote hookout
546 $ hg -q --config ui.ssh="python $TESTDIR/dummyssh" clone ssh://user@dummy/remote hookout
534 $ cd hookout
547 $ cd hookout
535 $ touch hookfailure
548 $ touch hookfailure
536 $ hg -q commit -A -m 'remote hook failure'
549 $ hg -q commit -A -m 'remote hook failure'
537 $ hg --config ui.ssh="python $TESTDIR/dummyssh" push
550 $ hg --config ui.ssh="python $TESTDIR/dummyssh" push
538 pushing to ssh://user@dummy/remote
551 pushing to ssh://user@dummy/remote
539 searching for changes
552 searching for changes
540 remote: adding changesets
553 remote: adding changesets
541 remote: adding manifests
554 remote: adding manifests
542 remote: adding file changes
555 remote: adding file changes
543 remote: added 1 changesets with 1 changes to 1 files
556 remote: added 1 changesets with 1 changes to 1 files
544 remote: hook failure!
557 remote: hook failure!
545 remote: transaction abort!
558 remote: transaction abort!
546 remote: rollback completed
559 remote: rollback completed
547 remote: pretxnchangegroup.fail hook failed
560 remote: pretxnchangegroup.fail hook failed
548 abort: push failed on remote
561 abort: push failed on remote
549 [255]
562 [255]
550
563
551 abort during pull is properly reported as such
564 abort during pull is properly reported as such
552
565
553 $ echo morefoo >> ../remote/foo
566 $ echo morefoo >> ../remote/foo
554 $ hg -R ../remote commit --message "more foo to be pulled"
567 $ hg -R ../remote commit --message "more foo to be pulled"
555 $ cat >> ../remote/.hg/hgrc << EOF
568 $ cat >> ../remote/.hg/hgrc << EOF
556 > [extensions]
569 > [extensions]
557 > crash = ${TESTDIR}/crashgetbundler.py
570 > crash = ${TESTDIR}/crashgetbundler.py
558 > EOF
571 > EOF
559 $ hg --config ui.ssh="python $TESTDIR/dummyssh" pull
572 $ hg --config ui.ssh="python $TESTDIR/dummyssh" pull
560 pulling from ssh://user@dummy/remote
573 pulling from ssh://user@dummy/remote
561 searching for changes
574 searching for changes
562 remote: abort: this is an exercise
575 remote: abort: this is an exercise
563 abort: pull failed on remote
576 abort: pull failed on remote
564 [255]
577 [255]
General Comments 0
You need to be logged in to leave comments. Login now