##// END OF EJS Templates
merge with stable
Matt Mackall -
r15027:1e45b92f merge default
parent child Browse files
Show More
@@ -1,729 +1,738 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 i18n import _
8 from i18n import _
9 import os, sys, atexit, signal, pdb, socket, errno, shlex, time, traceback, re
9 import os, sys, atexit, signal, pdb, socket, errno, shlex, time, traceback, re
10 import util, commands, hg, fancyopts, extensions, hook, error
10 import util, commands, hg, fancyopts, extensions, hook, error
11 import cmdutil, encoding
11 import cmdutil, encoding
12 import ui as uimod
12 import ui as uimod
13
13
14 class request(object):
14 class request(object):
15 def __init__(self, args, ui=None, repo=None, fin=None, fout=None, ferr=None):
15 def __init__(self, args, ui=None, repo=None, fin=None, fout=None, ferr=None):
16 self.args = args
16 self.args = args
17 self.ui = ui
17 self.ui = ui
18 self.repo = repo
18 self.repo = repo
19
19
20 # input/output/error streams
20 # input/output/error streams
21 self.fin = fin
21 self.fin = fin
22 self.fout = fout
22 self.fout = fout
23 self.ferr = ferr
23 self.ferr = ferr
24
24
25 def run():
25 def run():
26 "run the command in sys.argv"
26 "run the command in sys.argv"
27 sys.exit(dispatch(request(sys.argv[1:])))
27 sys.exit(dispatch(request(sys.argv[1:])))
28
28
29 def dispatch(req):
29 def dispatch(req):
30 "run the command specified in req.args"
30 "run the command specified in req.args"
31 if req.ferr:
31 if req.ferr:
32 ferr = req.ferr
32 ferr = req.ferr
33 elif req.ui:
33 elif req.ui:
34 ferr = req.ui.ferr
34 ferr = req.ui.ferr
35 else:
35 else:
36 ferr = sys.stderr
36 ferr = sys.stderr
37
37
38 try:
38 try:
39 if not req.ui:
39 if not req.ui:
40 req.ui = uimod.ui()
40 req.ui = uimod.ui()
41 if '--traceback' in req.args:
41 if '--traceback' in req.args:
42 req.ui.setconfig('ui', 'traceback', 'on')
42 req.ui.setconfig('ui', 'traceback', 'on')
43
43
44 # set ui streams from the request
44 # set ui streams from the request
45 if req.fin:
45 if req.fin:
46 req.ui.fin = req.fin
46 req.ui.fin = req.fin
47 if req.fout:
47 if req.fout:
48 req.ui.fout = req.fout
48 req.ui.fout = req.fout
49 if req.ferr:
49 if req.ferr:
50 req.ui.ferr = req.ferr
50 req.ui.ferr = req.ferr
51 except util.Abort, inst:
51 except util.Abort, inst:
52 ferr.write(_("abort: %s\n") % inst)
52 ferr.write(_("abort: %s\n") % inst)
53 if inst.hint:
53 if inst.hint:
54 ferr.write(_("(%s)\n") % inst.hint)
54 ferr.write(_("(%s)\n") % inst.hint)
55 return -1
55 return -1
56 except error.ParseError, inst:
56 except error.ParseError, inst:
57 if len(inst.args) > 1:
57 if len(inst.args) > 1:
58 ferr.write(_("hg: parse error at %s: %s\n") %
58 ferr.write(_("hg: parse error at %s: %s\n") %
59 (inst.args[1], inst.args[0]))
59 (inst.args[1], inst.args[0]))
60 else:
60 else:
61 ferr.write(_("hg: parse error: %s\n") % inst.args[0])
61 ferr.write(_("hg: parse error: %s\n") % inst.args[0])
62 return -1
62 return -1
63
63
64 return _runcatch(req)
64 return _runcatch(req)
65
65
66 def _runcatch(req):
66 def _runcatch(req):
67 def catchterm(*args):
67 def catchterm(*args):
68 raise error.SignalInterrupt
68 raise error.SignalInterrupt
69
69
70 ui = req.ui
70 ui = req.ui
71 try:
71 try:
72 for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM':
72 for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM':
73 num = getattr(signal, name, None)
73 num = getattr(signal, name, None)
74 if num:
74 if num:
75 signal.signal(num, catchterm)
75 signal.signal(num, catchterm)
76 except ValueError:
76 except ValueError:
77 pass # happens if called in a thread
77 pass # happens if called in a thread
78
78
79 try:
79 try:
80 try:
80 try:
81 # enter the debugger before command execution
81 # enter the debugger before command execution
82 if '--debugger' in req.args:
82 if '--debugger' in req.args:
83 ui.warn(_("entering debugger - "
83 ui.warn(_("entering debugger - "
84 "type c to continue starting hg or h for help\n"))
84 "type c to continue starting hg or h for help\n"))
85 pdb.set_trace()
85 pdb.set_trace()
86 try:
86 try:
87 return _dispatch(req)
87 return _dispatch(req)
88 finally:
88 finally:
89 ui.flush()
89 ui.flush()
90 except:
90 except:
91 # enter the debugger when we hit an exception
91 # enter the debugger when we hit an exception
92 if '--debugger' in req.args:
92 if '--debugger' in req.args:
93 traceback.print_exc()
93 traceback.print_exc()
94 pdb.post_mortem(sys.exc_info()[2])
94 pdb.post_mortem(sys.exc_info()[2])
95 ui.traceback()
95 ui.traceback()
96 raise
96 raise
97
97
98 # Global exception handling, alphabetically
98 # Global exception handling, alphabetically
99 # Mercurial-specific first, followed by built-in and library exceptions
99 # Mercurial-specific first, followed by built-in and library exceptions
100 except error.AmbiguousCommand, inst:
100 except error.AmbiguousCommand, inst:
101 ui.warn(_("hg: command '%s' is ambiguous:\n %s\n") %
101 ui.warn(_("hg: command '%s' is ambiguous:\n %s\n") %
102 (inst.args[0], " ".join(inst.args[1])))
102 (inst.args[0], " ".join(inst.args[1])))
103 except error.ParseError, inst:
103 except error.ParseError, inst:
104 if len(inst.args) > 1:
104 if len(inst.args) > 1:
105 ui.warn(_("hg: parse error at %s: %s\n") %
105 ui.warn(_("hg: parse error at %s: %s\n") %
106 (inst.args[1], inst.args[0]))
106 (inst.args[1], inst.args[0]))
107 else:
107 else:
108 ui.warn(_("hg: parse error: %s\n") % inst.args[0])
108 ui.warn(_("hg: parse error: %s\n") % inst.args[0])
109 return -1
109 return -1
110 except error.LockHeld, inst:
110 except error.LockHeld, inst:
111 if inst.errno == errno.ETIMEDOUT:
111 if inst.errno == errno.ETIMEDOUT:
112 reason = _('timed out waiting for lock held by %s') % inst.locker
112 reason = _('timed out waiting for lock held by %s') % inst.locker
113 else:
113 else:
114 reason = _('lock held by %s') % inst.locker
114 reason = _('lock held by %s') % inst.locker
115 ui.warn(_("abort: %s: %s\n") % (inst.desc or inst.filename, reason))
115 ui.warn(_("abort: %s: %s\n") % (inst.desc or inst.filename, reason))
116 except error.LockUnavailable, inst:
116 except error.LockUnavailable, inst:
117 ui.warn(_("abort: could not lock %s: %s\n") %
117 ui.warn(_("abort: could not lock %s: %s\n") %
118 (inst.desc or inst.filename, inst.strerror))
118 (inst.desc or inst.filename, inst.strerror))
119 except error.CommandError, inst:
119 except error.CommandError, inst:
120 if inst.args[0]:
120 if inst.args[0]:
121 ui.warn(_("hg %s: %s\n") % (inst.args[0], inst.args[1]))
121 ui.warn(_("hg %s: %s\n") % (inst.args[0], inst.args[1]))
122 commands.help_(ui, inst.args[0], full=False, command=True)
122 commands.help_(ui, inst.args[0], full=False, command=True)
123 else:
123 else:
124 ui.warn(_("hg: %s\n") % inst.args[1])
124 ui.warn(_("hg: %s\n") % inst.args[1])
125 commands.help_(ui, 'shortlist')
125 commands.help_(ui, 'shortlist')
126 except error.OutOfBandError, inst:
126 except error.OutOfBandError, inst:
127 ui.warn("abort: remote error:\n")
127 ui.warn("abort: remote error:\n")
128 ui.warn(''.join(inst.args))
128 ui.warn(''.join(inst.args))
129 except error.RepoError, inst:
129 except error.RepoError, inst:
130 ui.warn(_("abort: %s!\n") % inst)
130 ui.warn(_("abort: %s!\n") % inst)
131 if inst.hint:
131 if inst.hint:
132 ui.warn(_("(%s)\n") % inst.hint)
132 ui.warn(_("(%s)\n") % inst.hint)
133 except error.ResponseError, inst:
133 except error.ResponseError, inst:
134 ui.warn(_("abort: %s") % inst.args[0])
134 ui.warn(_("abort: %s") % inst.args[0])
135 if not isinstance(inst.args[1], basestring):
135 if not isinstance(inst.args[1], basestring):
136 ui.warn(" %r\n" % (inst.args[1],))
136 ui.warn(" %r\n" % (inst.args[1],))
137 elif not inst.args[1]:
137 elif not inst.args[1]:
138 ui.warn(_(" empty string\n"))
138 ui.warn(_(" empty string\n"))
139 else:
139 else:
140 ui.warn("\n%r\n" % util.ellipsis(inst.args[1]))
140 ui.warn("\n%r\n" % util.ellipsis(inst.args[1]))
141 except error.RevlogError, inst:
141 except error.RevlogError, inst:
142 ui.warn(_("abort: %s!\n") % inst)
142 ui.warn(_("abort: %s!\n") % inst)
143 except error.SignalInterrupt:
143 except error.SignalInterrupt:
144 ui.warn(_("killed!\n"))
144 ui.warn(_("killed!\n"))
145 except error.UnknownCommand, inst:
145 except error.UnknownCommand, inst:
146 ui.warn(_("hg: unknown command '%s'\n") % inst.args[0])
146 ui.warn(_("hg: unknown command '%s'\n") % inst.args[0])
147 try:
147 try:
148 # check if the command is in a disabled extension
148 # check if the command is in a disabled extension
149 # (but don't check for extensions themselves)
149 # (but don't check for extensions themselves)
150 commands.help_(ui, inst.args[0], unknowncmd=True)
150 commands.help_(ui, inst.args[0], unknowncmd=True)
151 except error.UnknownCommand:
151 except error.UnknownCommand:
152 commands.help_(ui, 'shortlist')
152 commands.help_(ui, 'shortlist')
153 except util.Abort, inst:
153 except util.Abort, inst:
154 ui.warn(_("abort: %s\n") % inst)
154 ui.warn(_("abort: %s\n") % inst)
155 if inst.hint:
155 if inst.hint:
156 ui.warn(_("(%s)\n") % inst.hint)
156 ui.warn(_("(%s)\n") % inst.hint)
157 except ImportError, inst:
157 except ImportError, inst:
158 ui.warn(_("abort: %s!\n") % inst)
158 ui.warn(_("abort: %s!\n") % inst)
159 m = str(inst).split()[-1]
159 m = str(inst).split()[-1]
160 if m in "mpatch bdiff".split():
160 if m in "mpatch bdiff".split():
161 ui.warn(_("(did you forget to compile extensions?)\n"))
161 ui.warn(_("(did you forget to compile extensions?)\n"))
162 elif m in "zlib".split():
162 elif m in "zlib".split():
163 ui.warn(_("(is your Python install correct?)\n"))
163 ui.warn(_("(is your Python install correct?)\n"))
164 except IOError, inst:
164 except IOError, inst:
165 if util.safehasattr(inst, "code"):
165 if util.safehasattr(inst, "code"):
166 ui.warn(_("abort: %s\n") % inst)
166 ui.warn(_("abort: %s\n") % inst)
167 elif util.safehasattr(inst, "reason"):
167 elif util.safehasattr(inst, "reason"):
168 try: # usually it is in the form (errno, strerror)
168 try: # usually it is in the form (errno, strerror)
169 reason = inst.reason.args[1]
169 reason = inst.reason.args[1]
170 except (AttributeError, IndexError):
170 except (AttributeError, IndexError):
171 # it might be anything, for example a string
171 # it might be anything, for example a string
172 reason = inst.reason
172 reason = inst.reason
173 ui.warn(_("abort: error: %s\n") % reason)
173 ui.warn(_("abort: error: %s\n") % reason)
174 elif util.safehasattr(inst, "args") and inst.args[0] == errno.EPIPE:
174 elif util.safehasattr(inst, "args") and inst.args[0] == errno.EPIPE:
175 if ui.debugflag:
175 if ui.debugflag:
176 ui.warn(_("broken pipe\n"))
176 ui.warn(_("broken pipe\n"))
177 elif getattr(inst, "strerror", None):
177 elif getattr(inst, "strerror", None):
178 if getattr(inst, "filename", None):
178 if getattr(inst, "filename", None):
179 ui.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
179 ui.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
180 else:
180 else:
181 ui.warn(_("abort: %s\n") % inst.strerror)
181 ui.warn(_("abort: %s\n") % inst.strerror)
182 else:
182 else:
183 raise
183 raise
184 except OSError, inst:
184 except OSError, inst:
185 if getattr(inst, "filename", None):
185 if getattr(inst, "filename", None):
186 ui.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
186 ui.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
187 else:
187 else:
188 ui.warn(_("abort: %s\n") % inst.strerror)
188 ui.warn(_("abort: %s\n") % inst.strerror)
189 except KeyboardInterrupt:
189 except KeyboardInterrupt:
190 try:
190 try:
191 ui.warn(_("interrupted!\n"))
191 ui.warn(_("interrupted!\n"))
192 except IOError, inst:
192 except IOError, inst:
193 if inst.errno == errno.EPIPE:
193 if inst.errno == errno.EPIPE:
194 if ui.debugflag:
194 if ui.debugflag:
195 ui.warn(_("\nbroken pipe\n"))
195 ui.warn(_("\nbroken pipe\n"))
196 else:
196 else:
197 raise
197 raise
198 except MemoryError:
198 except MemoryError:
199 ui.warn(_("abort: out of memory\n"))
199 ui.warn(_("abort: out of memory\n"))
200 except SystemExit, inst:
200 except SystemExit, inst:
201 # Commands shouldn't sys.exit directly, but give a return code.
201 # Commands shouldn't sys.exit directly, but give a return code.
202 # Just in case catch this and and pass exit code to caller.
202 # Just in case catch this and and pass exit code to caller.
203 return inst.code
203 return inst.code
204 except socket.error, inst:
204 except socket.error, inst:
205 ui.warn(_("abort: %s\n") % inst.args[-1])
205 ui.warn(_("abort: %s\n") % inst.args[-1])
206 except:
206 except:
207 ui.warn(_("** unknown exception encountered,"
207 ui.warn(_("** unknown exception encountered,"
208 " please report by visiting\n"))
208 " please report by visiting\n"))
209 ui.warn(_("** http://mercurial.selenic.com/wiki/BugTracker\n"))
209 ui.warn(_("** http://mercurial.selenic.com/wiki/BugTracker\n"))
210 ui.warn(_("** Python %s\n") % sys.version.replace('\n', ''))
210 ui.warn(_("** Python %s\n") % sys.version.replace('\n', ''))
211 ui.warn(_("** Mercurial Distributed SCM (version %s)\n")
211 ui.warn(_("** Mercurial Distributed SCM (version %s)\n")
212 % util.version())
212 % util.version())
213 ui.warn(_("** Extensions loaded: %s\n")
213 ui.warn(_("** Extensions loaded: %s\n")
214 % ", ".join([x[0] for x in extensions.extensions()]))
214 % ", ".join([x[0] for x in extensions.extensions()]))
215 raise
215 raise
216
216
217 return -1
217 return -1
218
218
219 def aliasargs(fn, givenargs):
219 def aliasargs(fn, givenargs):
220 args = getattr(fn, 'args', [])
220 args = getattr(fn, 'args', [])
221 if args and givenargs:
221 if args and givenargs:
222 cmd = ' '.join(map(util.shellquote, args))
222 cmd = ' '.join(map(util.shellquote, args))
223
223
224 nums = []
224 nums = []
225 def replacer(m):
225 def replacer(m):
226 num = int(m.group(1)) - 1
226 num = int(m.group(1)) - 1
227 nums.append(num)
227 nums.append(num)
228 return givenargs[num]
228 return givenargs[num]
229 cmd = re.sub(r'\$(\d+|\$)', replacer, cmd)
229 cmd = re.sub(r'\$(\d+|\$)', replacer, cmd)
230 givenargs = [x for i, x in enumerate(givenargs)
230 givenargs = [x for i, x in enumerate(givenargs)
231 if i not in nums]
231 if i not in nums]
232 args = shlex.split(cmd)
232 args = shlex.split(cmd)
233 return args + givenargs
233 return args + givenargs
234
234
235 class cmdalias(object):
235 class cmdalias(object):
236 def __init__(self, name, definition, cmdtable):
236 def __init__(self, name, definition, cmdtable):
237 self.name = self.cmd = name
237 self.name = self.cmd = name
238 self.cmdname = ''
238 self.cmdname = ''
239 self.definition = definition
239 self.definition = definition
240 self.args = []
240 self.args = []
241 self.opts = []
241 self.opts = []
242 self.help = ''
242 self.help = ''
243 self.norepo = True
243 self.norepo = True
244 self.badalias = False
244 self.badalias = False
245
245
246 try:
246 try:
247 aliases, entry = cmdutil.findcmd(self.name, cmdtable)
247 aliases, entry = cmdutil.findcmd(self.name, cmdtable)
248 for alias, e in cmdtable.iteritems():
248 for alias, e in cmdtable.iteritems():
249 if e is entry:
249 if e is entry:
250 self.cmd = alias
250 self.cmd = alias
251 break
251 break
252 self.shadows = True
252 self.shadows = True
253 except error.UnknownCommand:
253 except error.UnknownCommand:
254 self.shadows = False
254 self.shadows = False
255
255
256 if not self.definition:
256 if not self.definition:
257 def fn(ui, *args):
257 def fn(ui, *args):
258 ui.warn(_("no definition for alias '%s'\n") % self.name)
258 ui.warn(_("no definition for alias '%s'\n") % self.name)
259 return 1
259 return 1
260 self.fn = fn
260 self.fn = fn
261 self.badalias = True
261 self.badalias = True
262
262
263 return
263 return
264
264
265 if self.definition.startswith('!'):
265 if self.definition.startswith('!'):
266 self.shell = True
266 self.shell = True
267 def fn(ui, *args):
267 def fn(ui, *args):
268 env = {'HG_ARGS': ' '.join((self.name,) + args)}
268 env = {'HG_ARGS': ' '.join((self.name,) + args)}
269 def _checkvar(m):
269 def _checkvar(m):
270 if m.groups()[0] == '$':
270 if m.groups()[0] == '$':
271 return m.group()
271 return m.group()
272 elif int(m.groups()[0]) <= len(args):
272 elif int(m.groups()[0]) <= len(args):
273 return m.group()
273 return m.group()
274 else:
274 else:
275 ui.debug("No argument found for substitution "
275 ui.debug("No argument found for substitution "
276 "of %i variable in alias '%s' definition."
276 "of %i variable in alias '%s' definition."
277 % (int(m.groups()[0]), self.name))
277 % (int(m.groups()[0]), self.name))
278 return ''
278 return ''
279 cmd = re.sub(r'\$(\d+|\$)', _checkvar, self.definition[1:])
279 cmd = re.sub(r'\$(\d+|\$)', _checkvar, self.definition[1:])
280 replace = dict((str(i + 1), arg) for i, arg in enumerate(args))
280 replace = dict((str(i + 1), arg) for i, arg in enumerate(args))
281 replace['0'] = self.name
281 replace['0'] = self.name
282 replace['@'] = ' '.join(args)
282 replace['@'] = ' '.join(args)
283 cmd = util.interpolate(r'\$', replace, cmd, escape_prefix=True)
283 cmd = util.interpolate(r'\$', replace, cmd, escape_prefix=True)
284 return util.system(cmd, environ=env, out=ui.fout)
284 return util.system(cmd, environ=env, out=ui.fout)
285 self.fn = fn
285 self.fn = fn
286 return
286 return
287
287
288 args = shlex.split(self.definition)
288 args = shlex.split(self.definition)
289 self.cmdname = cmd = args.pop(0)
289 self.cmdname = cmd = args.pop(0)
290 args = map(util.expandpath, args)
290 args = map(util.expandpath, args)
291
291
292 for invalidarg in ("--cwd", "-R", "--repository", "--repo"):
292 for invalidarg in ("--cwd", "-R", "--repository", "--repo"):
293 if _earlygetopt([invalidarg], args):
293 if _earlygetopt([invalidarg], args):
294 def fn(ui, *args):
294 def fn(ui, *args):
295 ui.warn(_("error in definition for alias '%s': %s may only "
295 ui.warn(_("error in definition for alias '%s': %s may only "
296 "be given on the command line\n")
296 "be given on the command line\n")
297 % (self.name, invalidarg))
297 % (self.name, invalidarg))
298 return 1
298 return 1
299
299
300 self.fn = fn
300 self.fn = fn
301 self.badalias = True
301 self.badalias = True
302 return
302 return
303
303
304 try:
304 try:
305 tableentry = cmdutil.findcmd(cmd, cmdtable, False)[1]
305 tableentry = cmdutil.findcmd(cmd, cmdtable, False)[1]
306 if len(tableentry) > 2:
306 if len(tableentry) > 2:
307 self.fn, self.opts, self.help = tableentry
307 self.fn, self.opts, self.help = tableentry
308 else:
308 else:
309 self.fn, self.opts = tableentry
309 self.fn, self.opts = tableentry
310
310
311 self.args = aliasargs(self.fn, args)
311 self.args = aliasargs(self.fn, args)
312 if cmd not in commands.norepo.split(' '):
312 if cmd not in commands.norepo.split(' '):
313 self.norepo = False
313 self.norepo = False
314 if self.help.startswith("hg " + cmd):
314 if self.help.startswith("hg " + cmd):
315 # drop prefix in old-style help lines so hg shows the alias
315 # drop prefix in old-style help lines so hg shows the alias
316 self.help = self.help[4 + len(cmd):]
316 self.help = self.help[4 + len(cmd):]
317 self.__doc__ = self.fn.__doc__
317 self.__doc__ = self.fn.__doc__
318
318
319 except error.UnknownCommand:
319 except error.UnknownCommand:
320 def fn(ui, *args):
320 def fn(ui, *args):
321 ui.warn(_("alias '%s' resolves to unknown command '%s'\n") \
321 ui.warn(_("alias '%s' resolves to unknown command '%s'\n") \
322 % (self.name, cmd))
322 % (self.name, cmd))
323 try:
323 try:
324 # check if the command is in a disabled extension
324 # check if the command is in a disabled extension
325 commands.help_(ui, cmd, unknowncmd=True)
325 commands.help_(ui, cmd, unknowncmd=True)
326 except error.UnknownCommand:
326 except error.UnknownCommand:
327 pass
327 pass
328 return 1
328 return 1
329 self.fn = fn
329 self.fn = fn
330 self.badalias = True
330 self.badalias = True
331 except error.AmbiguousCommand:
331 except error.AmbiguousCommand:
332 def fn(ui, *args):
332 def fn(ui, *args):
333 ui.warn(_("alias '%s' resolves to ambiguous command '%s'\n") \
333 ui.warn(_("alias '%s' resolves to ambiguous command '%s'\n") \
334 % (self.name, cmd))
334 % (self.name, cmd))
335 return 1
335 return 1
336 self.fn = fn
336 self.fn = fn
337 self.badalias = True
337 self.badalias = True
338
338
339 def __call__(self, ui, *args, **opts):
339 def __call__(self, ui, *args, **opts):
340 if self.shadows:
340 if self.shadows:
341 ui.debug("alias '%s' shadows command '%s'\n" %
341 ui.debug("alias '%s' shadows command '%s'\n" %
342 (self.name, self.cmdname))
342 (self.name, self.cmdname))
343
343
344 if util.safehasattr(self, 'shell'):
344 if util.safehasattr(self, 'shell'):
345 return self.fn(ui, *args, **opts)
345 return self.fn(ui, *args, **opts)
346 else:
346 else:
347 try:
347 try:
348 util.checksignature(self.fn)(ui, *args, **opts)
348 util.checksignature(self.fn)(ui, *args, **opts)
349 except error.SignatureError:
349 except error.SignatureError:
350 args = ' '.join([self.cmdname] + self.args)
350 args = ' '.join([self.cmdname] + self.args)
351 ui.debug("alias '%s' expands to '%s'\n" % (self.name, args))
351 ui.debug("alias '%s' expands to '%s'\n" % (self.name, args))
352 raise
352 raise
353
353
354 def addaliases(ui, cmdtable):
354 def addaliases(ui, cmdtable):
355 # aliases are processed after extensions have been loaded, so they
355 # aliases are processed after extensions have been loaded, so they
356 # may use extension commands. Aliases can also use other alias definitions,
356 # may use extension commands. Aliases can also use other alias definitions,
357 # but only if they have been defined prior to the current definition.
357 # but only if they have been defined prior to the current definition.
358 for alias, definition in ui.configitems('alias'):
358 for alias, definition in ui.configitems('alias'):
359 aliasdef = cmdalias(alias, definition, cmdtable)
359 aliasdef = cmdalias(alias, definition, cmdtable)
360
361 try:
362 olddef = cmdtable[aliasdef.cmd][0]
363 if olddef.definition == aliasdef.definition:
364 continue
365 except (KeyError, AttributeError):
366 # definition might not exist or it might not be a cmdalias
367 pass
368
360 cmdtable[aliasdef.cmd] = (aliasdef, aliasdef.opts, aliasdef.help)
369 cmdtable[aliasdef.cmd] = (aliasdef, aliasdef.opts, aliasdef.help)
361 if aliasdef.norepo:
370 if aliasdef.norepo:
362 commands.norepo += ' %s' % alias
371 commands.norepo += ' %s' % alias
363
372
364 def _parse(ui, args):
373 def _parse(ui, args):
365 options = {}
374 options = {}
366 cmdoptions = {}
375 cmdoptions = {}
367
376
368 try:
377 try:
369 args = fancyopts.fancyopts(args, commands.globalopts, options)
378 args = fancyopts.fancyopts(args, commands.globalopts, options)
370 except fancyopts.getopt.GetoptError, inst:
379 except fancyopts.getopt.GetoptError, inst:
371 raise error.CommandError(None, inst)
380 raise error.CommandError(None, inst)
372
381
373 if args:
382 if args:
374 cmd, args = args[0], args[1:]
383 cmd, args = args[0], args[1:]
375 aliases, entry = cmdutil.findcmd(cmd, commands.table,
384 aliases, entry = cmdutil.findcmd(cmd, commands.table,
376 ui.config("ui", "strict"))
385 ui.config("ui", "strict"))
377 cmd = aliases[0]
386 cmd = aliases[0]
378 args = aliasargs(entry[0], args)
387 args = aliasargs(entry[0], args)
379 defaults = ui.config("defaults", cmd)
388 defaults = ui.config("defaults", cmd)
380 if defaults:
389 if defaults:
381 args = map(util.expandpath, shlex.split(defaults)) + args
390 args = map(util.expandpath, shlex.split(defaults)) + args
382 c = list(entry[1])
391 c = list(entry[1])
383 else:
392 else:
384 cmd = None
393 cmd = None
385 c = []
394 c = []
386
395
387 # combine global options into local
396 # combine global options into local
388 for o in commands.globalopts:
397 for o in commands.globalopts:
389 c.append((o[0], o[1], options[o[1]], o[3]))
398 c.append((o[0], o[1], options[o[1]], o[3]))
390
399
391 try:
400 try:
392 args = fancyopts.fancyopts(args, c, cmdoptions, True)
401 args = fancyopts.fancyopts(args, c, cmdoptions, True)
393 except fancyopts.getopt.GetoptError, inst:
402 except fancyopts.getopt.GetoptError, inst:
394 raise error.CommandError(cmd, inst)
403 raise error.CommandError(cmd, inst)
395
404
396 # separate global options back out
405 # separate global options back out
397 for o in commands.globalopts:
406 for o in commands.globalopts:
398 n = o[1]
407 n = o[1]
399 options[n] = cmdoptions[n]
408 options[n] = cmdoptions[n]
400 del cmdoptions[n]
409 del cmdoptions[n]
401
410
402 return (cmd, cmd and entry[0] or None, args, options, cmdoptions)
411 return (cmd, cmd and entry[0] or None, args, options, cmdoptions)
403
412
404 def _parseconfig(ui, config):
413 def _parseconfig(ui, config):
405 """parse the --config options from the command line"""
414 """parse the --config options from the command line"""
406 configs = []
415 configs = []
407
416
408 for cfg in config:
417 for cfg in config:
409 try:
418 try:
410 name, value = cfg.split('=', 1)
419 name, value = cfg.split('=', 1)
411 section, name = name.split('.', 1)
420 section, name = name.split('.', 1)
412 if not section or not name:
421 if not section or not name:
413 raise IndexError
422 raise IndexError
414 ui.setconfig(section, name, value)
423 ui.setconfig(section, name, value)
415 configs.append((section, name, value))
424 configs.append((section, name, value))
416 except (IndexError, ValueError):
425 except (IndexError, ValueError):
417 raise util.Abort(_('malformed --config option: %r '
426 raise util.Abort(_('malformed --config option: %r '
418 '(use --config section.name=value)') % cfg)
427 '(use --config section.name=value)') % cfg)
419
428
420 return configs
429 return configs
421
430
422 def _earlygetopt(aliases, args):
431 def _earlygetopt(aliases, args):
423 """Return list of values for an option (or aliases).
432 """Return list of values for an option (or aliases).
424
433
425 The values are listed in the order they appear in args.
434 The values are listed in the order they appear in args.
426 The options and values are removed from args.
435 The options and values are removed from args.
427 """
436 """
428 try:
437 try:
429 argcount = args.index("--")
438 argcount = args.index("--")
430 except ValueError:
439 except ValueError:
431 argcount = len(args)
440 argcount = len(args)
432 shortopts = [opt for opt in aliases if len(opt) == 2]
441 shortopts = [opt for opt in aliases if len(opt) == 2]
433 values = []
442 values = []
434 pos = 0
443 pos = 0
435 while pos < argcount:
444 while pos < argcount:
436 if args[pos] in aliases:
445 if args[pos] in aliases:
437 if pos + 1 >= argcount:
446 if pos + 1 >= argcount:
438 # ignore and let getopt report an error if there is no value
447 # ignore and let getopt report an error if there is no value
439 break
448 break
440 del args[pos]
449 del args[pos]
441 values.append(args.pop(pos))
450 values.append(args.pop(pos))
442 argcount -= 2
451 argcount -= 2
443 elif args[pos][:2] in shortopts:
452 elif args[pos][:2] in shortopts:
444 # short option can have no following space, e.g. hg log -Rfoo
453 # short option can have no following space, e.g. hg log -Rfoo
445 values.append(args.pop(pos)[2:])
454 values.append(args.pop(pos)[2:])
446 argcount -= 1
455 argcount -= 1
447 else:
456 else:
448 pos += 1
457 pos += 1
449 return values
458 return values
450
459
451 def runcommand(lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions):
460 def runcommand(lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions):
452 # run pre-hook, and abort if it fails
461 # run pre-hook, and abort if it fails
453 ret = hook.hook(lui, repo, "pre-%s" % cmd, False, args=" ".join(fullargs),
462 ret = hook.hook(lui, repo, "pre-%s" % cmd, False, args=" ".join(fullargs),
454 pats=cmdpats, opts=cmdoptions)
463 pats=cmdpats, opts=cmdoptions)
455 if ret:
464 if ret:
456 return ret
465 return ret
457 ret = _runcommand(ui, options, cmd, d)
466 ret = _runcommand(ui, options, cmd, d)
458 # run post-hook, passing command result
467 # run post-hook, passing command result
459 hook.hook(lui, repo, "post-%s" % cmd, False, args=" ".join(fullargs),
468 hook.hook(lui, repo, "post-%s" % cmd, False, args=" ".join(fullargs),
460 result=ret, pats=cmdpats, opts=cmdoptions)
469 result=ret, pats=cmdpats, opts=cmdoptions)
461 return ret
470 return ret
462
471
463 def _getlocal(ui, rpath):
472 def _getlocal(ui, rpath):
464 """Return (path, local ui object) for the given target path.
473 """Return (path, local ui object) for the given target path.
465
474
466 Takes paths in [cwd]/.hg/hgrc into account."
475 Takes paths in [cwd]/.hg/hgrc into account."
467 """
476 """
468 try:
477 try:
469 wd = os.getcwd()
478 wd = os.getcwd()
470 except OSError, e:
479 except OSError, e:
471 raise util.Abort(_("error getting current working directory: %s") %
480 raise util.Abort(_("error getting current working directory: %s") %
472 e.strerror)
481 e.strerror)
473 path = cmdutil.findrepo(wd) or ""
482 path = cmdutil.findrepo(wd) or ""
474 if not path:
483 if not path:
475 lui = ui
484 lui = ui
476 else:
485 else:
477 lui = ui.copy()
486 lui = ui.copy()
478 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
487 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
479
488
480 if rpath and rpath[-1]:
489 if rpath and rpath[-1]:
481 path = lui.expandpath(rpath[-1])
490 path = lui.expandpath(rpath[-1])
482 lui = ui.copy()
491 lui = ui.copy()
483 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
492 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
484
493
485 return path, lui
494 return path, lui
486
495
487 def _checkshellalias(lui, ui, args):
496 def _checkshellalias(lui, ui, args):
488 norepo = commands.norepo
497 norepo = commands.norepo
489 options = {}
498 options = {}
490
499
491 try:
500 try:
492 args = fancyopts.fancyopts(args, commands.globalopts, options)
501 args = fancyopts.fancyopts(args, commands.globalopts, options)
493 except fancyopts.getopt.GetoptError:
502 except fancyopts.getopt.GetoptError:
494 return
503 return
495
504
496 if not args:
505 if not args:
497 return
506 return
498
507
499 cmdtable = commands.table.copy()
508 cmdtable = commands.table.copy()
500 addaliases(lui, cmdtable)
509 addaliases(lui, cmdtable)
501
510
502 cmd = args[0]
511 cmd = args[0]
503 try:
512 try:
504 aliases, entry = cmdutil.findcmd(cmd, cmdtable, lui.config("ui", "strict"))
513 aliases, entry = cmdutil.findcmd(cmd, cmdtable, lui.config("ui", "strict"))
505 except (error.AmbiguousCommand, error.UnknownCommand):
514 except (error.AmbiguousCommand, error.UnknownCommand):
506 commands.norepo = norepo
515 commands.norepo = norepo
507 return
516 return
508
517
509 cmd = aliases[0]
518 cmd = aliases[0]
510 fn = entry[0]
519 fn = entry[0]
511
520
512 if cmd and util.safehasattr(fn, 'shell'):
521 if cmd and util.safehasattr(fn, 'shell'):
513 d = lambda: fn(ui, *args[1:])
522 d = lambda: fn(ui, *args[1:])
514 return lambda: runcommand(lui, None, cmd, args[:1], ui, options, d, [], {})
523 return lambda: runcommand(lui, None, cmd, args[:1], ui, options, d, [], {})
515
524
516 commands.norepo = norepo
525 commands.norepo = norepo
517
526
518 _loaded = set()
527 _loaded = set()
519 def _dispatch(req):
528 def _dispatch(req):
520 args = req.args
529 args = req.args
521 ui = req.ui
530 ui = req.ui
522
531
523 # read --config before doing anything else
532 # read --config before doing anything else
524 # (e.g. to change trust settings for reading .hg/hgrc)
533 # (e.g. to change trust settings for reading .hg/hgrc)
525 cfgs = _parseconfig(ui, _earlygetopt(['--config'], args))
534 cfgs = _parseconfig(ui, _earlygetopt(['--config'], args))
526
535
527 # check for cwd
536 # check for cwd
528 cwd = _earlygetopt(['--cwd'], args)
537 cwd = _earlygetopt(['--cwd'], args)
529 if cwd:
538 if cwd:
530 os.chdir(cwd[-1])
539 os.chdir(cwd[-1])
531
540
532 rpath = _earlygetopt(["-R", "--repository", "--repo"], args)
541 rpath = _earlygetopt(["-R", "--repository", "--repo"], args)
533 path, lui = _getlocal(ui, rpath)
542 path, lui = _getlocal(ui, rpath)
534
543
535 # Now that we're operating in the right directory/repository with
544 # Now that we're operating in the right directory/repository with
536 # the right config settings, check for shell aliases
545 # the right config settings, check for shell aliases
537 shellaliasfn = _checkshellalias(lui, ui, args)
546 shellaliasfn = _checkshellalias(lui, ui, args)
538 if shellaliasfn:
547 if shellaliasfn:
539 return shellaliasfn()
548 return shellaliasfn()
540
549
541 # Configure extensions in phases: uisetup, extsetup, cmdtable, and
550 # Configure extensions in phases: uisetup, extsetup, cmdtable, and
542 # reposetup. Programs like TortoiseHg will call _dispatch several
551 # reposetup. Programs like TortoiseHg will call _dispatch several
543 # times so we keep track of configured extensions in _loaded.
552 # times so we keep track of configured extensions in _loaded.
544 extensions.loadall(lui)
553 extensions.loadall(lui)
545 exts = [ext for ext in extensions.extensions() if ext[0] not in _loaded]
554 exts = [ext for ext in extensions.extensions() if ext[0] not in _loaded]
546 # Propagate any changes to lui.__class__ by extensions
555 # Propagate any changes to lui.__class__ by extensions
547 ui.__class__ = lui.__class__
556 ui.__class__ = lui.__class__
548
557
549 # (uisetup and extsetup are handled in extensions.loadall)
558 # (uisetup and extsetup are handled in extensions.loadall)
550
559
551 for name, module in exts:
560 for name, module in exts:
552 cmdtable = getattr(module, 'cmdtable', {})
561 cmdtable = getattr(module, 'cmdtable', {})
553 overrides = [cmd for cmd in cmdtable if cmd in commands.table]
562 overrides = [cmd for cmd in cmdtable if cmd in commands.table]
554 if overrides:
563 if overrides:
555 ui.warn(_("extension '%s' overrides commands: %s\n")
564 ui.warn(_("extension '%s' overrides commands: %s\n")
556 % (name, " ".join(overrides)))
565 % (name, " ".join(overrides)))
557 commands.table.update(cmdtable)
566 commands.table.update(cmdtable)
558 _loaded.add(name)
567 _loaded.add(name)
559
568
560 # (reposetup is handled in hg.repository)
569 # (reposetup is handled in hg.repository)
561
570
562 addaliases(lui, commands.table)
571 addaliases(lui, commands.table)
563
572
564 # check for fallback encoding
573 # check for fallback encoding
565 fallback = lui.config('ui', 'fallbackencoding')
574 fallback = lui.config('ui', 'fallbackencoding')
566 if fallback:
575 if fallback:
567 encoding.fallbackencoding = fallback
576 encoding.fallbackencoding = fallback
568
577
569 fullargs = args
578 fullargs = args
570 cmd, func, args, options, cmdoptions = _parse(lui, args)
579 cmd, func, args, options, cmdoptions = _parse(lui, args)
571
580
572 if options["config"]:
581 if options["config"]:
573 raise util.Abort(_("option --config may not be abbreviated!"))
582 raise util.Abort(_("option --config may not be abbreviated!"))
574 if options["cwd"]:
583 if options["cwd"]:
575 raise util.Abort(_("option --cwd may not be abbreviated!"))
584 raise util.Abort(_("option --cwd may not be abbreviated!"))
576 if options["repository"]:
585 if options["repository"]:
577 raise util.Abort(_(
586 raise util.Abort(_(
578 "Option -R has to be separated from other options (e.g. not -qR) "
587 "Option -R has to be separated from other options (e.g. not -qR) "
579 "and --repository may only be abbreviated as --repo!"))
588 "and --repository may only be abbreviated as --repo!"))
580
589
581 if options["encoding"]:
590 if options["encoding"]:
582 encoding.encoding = options["encoding"]
591 encoding.encoding = options["encoding"]
583 if options["encodingmode"]:
592 if options["encodingmode"]:
584 encoding.encodingmode = options["encodingmode"]
593 encoding.encodingmode = options["encodingmode"]
585 if options["time"]:
594 if options["time"]:
586 def get_times():
595 def get_times():
587 t = os.times()
596 t = os.times()
588 if t[4] == 0.0: # Windows leaves this as zero, so use time.clock()
597 if t[4] == 0.0: # Windows leaves this as zero, so use time.clock()
589 t = (t[0], t[1], t[2], t[3], time.clock())
598 t = (t[0], t[1], t[2], t[3], time.clock())
590 return t
599 return t
591 s = get_times()
600 s = get_times()
592 def print_time():
601 def print_time():
593 t = get_times()
602 t = get_times()
594 ui.warn(_("Time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
603 ui.warn(_("Time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
595 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
604 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
596 atexit.register(print_time)
605 atexit.register(print_time)
597
606
598 uis = set([ui, lui])
607 uis = set([ui, lui])
599
608
600 if req.repo:
609 if req.repo:
601 uis.add(req.repo.ui)
610 uis.add(req.repo.ui)
602
611
603 # copy configs that were passed on the cmdline (--config) to the repo ui
612 # copy configs that were passed on the cmdline (--config) to the repo ui
604 for cfg in cfgs:
613 for cfg in cfgs:
605 req.repo.ui.setconfig(*cfg)
614 req.repo.ui.setconfig(*cfg)
606
615
607 if options['verbose'] or options['debug'] or options['quiet']:
616 if options['verbose'] or options['debug'] or options['quiet']:
608 for opt in ('verbose', 'debug', 'quiet'):
617 for opt in ('verbose', 'debug', 'quiet'):
609 val = str(bool(options[opt]))
618 val = str(bool(options[opt]))
610 for ui_ in uis:
619 for ui_ in uis:
611 ui_.setconfig('ui', opt, val)
620 ui_.setconfig('ui', opt, val)
612
621
613 if options['traceback']:
622 if options['traceback']:
614 for ui_ in uis:
623 for ui_ in uis:
615 ui_.setconfig('ui', 'traceback', 'on')
624 ui_.setconfig('ui', 'traceback', 'on')
616
625
617 if options['noninteractive']:
626 if options['noninteractive']:
618 for ui_ in uis:
627 for ui_ in uis:
619 ui_.setconfig('ui', 'interactive', 'off')
628 ui_.setconfig('ui', 'interactive', 'off')
620
629
621 if cmdoptions.get('insecure', False):
630 if cmdoptions.get('insecure', False):
622 for ui_ in uis:
631 for ui_ in uis:
623 ui_.setconfig('web', 'cacerts', '')
632 ui_.setconfig('web', 'cacerts', '')
624
633
625 if options['version']:
634 if options['version']:
626 return commands.version_(ui)
635 return commands.version_(ui)
627 if options['help']:
636 if options['help']:
628 return commands.help_(ui, cmd)
637 return commands.help_(ui, cmd)
629 elif not cmd:
638 elif not cmd:
630 return commands.help_(ui, 'shortlist')
639 return commands.help_(ui, 'shortlist')
631
640
632 repo = None
641 repo = None
633 cmdpats = args[:]
642 cmdpats = args[:]
634 if cmd not in commands.norepo.split():
643 if cmd not in commands.norepo.split():
635 # use the repo from the request only if we don't have -R
644 # use the repo from the request only if we don't have -R
636 if not rpath and not cwd:
645 if not rpath and not cwd:
637 repo = req.repo
646 repo = req.repo
638
647
639 if repo:
648 if repo:
640 # set the descriptors of the repo ui to those of ui
649 # set the descriptors of the repo ui to those of ui
641 repo.ui.fin = ui.fin
650 repo.ui.fin = ui.fin
642 repo.ui.fout = ui.fout
651 repo.ui.fout = ui.fout
643 repo.ui.ferr = ui.ferr
652 repo.ui.ferr = ui.ferr
644 else:
653 else:
645 try:
654 try:
646 repo = hg.repository(ui, path=path)
655 repo = hg.repository(ui, path=path)
647 if not repo.local():
656 if not repo.local():
648 raise util.Abort(_("repository '%s' is not local") % path)
657 raise util.Abort(_("repository '%s' is not local") % path)
649 repo.ui.setconfig("bundle", "mainreporoot", repo.root)
658 repo.ui.setconfig("bundle", "mainreporoot", repo.root)
650 except error.RequirementError:
659 except error.RequirementError:
651 raise
660 raise
652 except error.RepoError:
661 except error.RepoError:
653 if cmd not in commands.optionalrepo.split():
662 if cmd not in commands.optionalrepo.split():
654 if args and not path: # try to infer -R from command args
663 if args and not path: # try to infer -R from command args
655 repos = map(cmdutil.findrepo, args)
664 repos = map(cmdutil.findrepo, args)
656 guess = repos[0]
665 guess = repos[0]
657 if guess and repos.count(guess) == len(repos):
666 if guess and repos.count(guess) == len(repos):
658 req.args = ['--repository', guess] + fullargs
667 req.args = ['--repository', guess] + fullargs
659 return _dispatch(req)
668 return _dispatch(req)
660 if not path:
669 if not path:
661 raise error.RepoError(_("no repository found in '%s'"
670 raise error.RepoError(_("no repository found in '%s'"
662 " (.hg not found)") % os.getcwd())
671 " (.hg not found)") % os.getcwd())
663 raise
672 raise
664 if repo:
673 if repo:
665 ui = repo.ui
674 ui = repo.ui
666 args.insert(0, repo)
675 args.insert(0, repo)
667 elif rpath:
676 elif rpath:
668 ui.warn(_("warning: --repository ignored\n"))
677 ui.warn(_("warning: --repository ignored\n"))
669
678
670 msg = ' '.join(' ' in a and repr(a) or a for a in fullargs)
679 msg = ' '.join(' ' in a and repr(a) or a for a in fullargs)
671 ui.log("command", msg + "\n")
680 ui.log("command", msg + "\n")
672 d = lambda: util.checksignature(func)(ui, *args, **cmdoptions)
681 d = lambda: util.checksignature(func)(ui, *args, **cmdoptions)
673 try:
682 try:
674 return runcommand(lui, repo, cmd, fullargs, ui, options, d,
683 return runcommand(lui, repo, cmd, fullargs, ui, options, d,
675 cmdpats, cmdoptions)
684 cmdpats, cmdoptions)
676 finally:
685 finally:
677 if repo and repo != req.repo:
686 if repo and repo != req.repo:
678 repo.close()
687 repo.close()
679
688
680 def _runcommand(ui, options, cmd, cmdfunc):
689 def _runcommand(ui, options, cmd, cmdfunc):
681 def checkargs():
690 def checkargs():
682 try:
691 try:
683 return cmdfunc()
692 return cmdfunc()
684 except error.SignatureError:
693 except error.SignatureError:
685 raise error.CommandError(cmd, _("invalid arguments"))
694 raise error.CommandError(cmd, _("invalid arguments"))
686
695
687 if options['profile']:
696 if options['profile']:
688 format = ui.config('profiling', 'format', default='text')
697 format = ui.config('profiling', 'format', default='text')
689
698
690 if not format in ['text', 'kcachegrind']:
699 if not format in ['text', 'kcachegrind']:
691 ui.warn(_("unrecognized profiling format '%s'"
700 ui.warn(_("unrecognized profiling format '%s'"
692 " - Ignored\n") % format)
701 " - Ignored\n") % format)
693 format = 'text'
702 format = 'text'
694
703
695 output = ui.config('profiling', 'output')
704 output = ui.config('profiling', 'output')
696
705
697 if output:
706 if output:
698 path = ui.expandpath(output)
707 path = ui.expandpath(output)
699 ostream = open(path, 'wb')
708 ostream = open(path, 'wb')
700 else:
709 else:
701 ostream = sys.stderr
710 ostream = sys.stderr
702
711
703 try:
712 try:
704 from mercurial import lsprof
713 from mercurial import lsprof
705 except ImportError:
714 except ImportError:
706 raise util.Abort(_(
715 raise util.Abort(_(
707 'lsprof not available - install from '
716 'lsprof not available - install from '
708 'http://codespeak.net/svn/user/arigo/hack/misc/lsprof/'))
717 'http://codespeak.net/svn/user/arigo/hack/misc/lsprof/'))
709 p = lsprof.Profiler()
718 p = lsprof.Profiler()
710 p.enable(subcalls=True)
719 p.enable(subcalls=True)
711 try:
720 try:
712 return checkargs()
721 return checkargs()
713 finally:
722 finally:
714 p.disable()
723 p.disable()
715
724
716 if format == 'kcachegrind':
725 if format == 'kcachegrind':
717 import lsprofcalltree
726 import lsprofcalltree
718 calltree = lsprofcalltree.KCacheGrind(p)
727 calltree = lsprofcalltree.KCacheGrind(p)
719 calltree.output(ostream)
728 calltree.output(ostream)
720 else:
729 else:
721 # format == 'text'
730 # format == 'text'
722 stats = lsprof.Stats(p.getstats())
731 stats = lsprof.Stats(p.getstats())
723 stats.sort()
732 stats.sort()
724 stats.pprint(top=10, file=ostream, climit=5)
733 stats.pprint(top=10, file=ostream, climit=5)
725
734
726 if output:
735 if output:
727 ostream.close()
736 ostream.close()
728 else:
737 else:
729 return checkargs()
738 return checkargs()
@@ -1,282 +1,282 b''
1 # httpconnection.py - urllib2 handler for new http support
1 # httpconnection.py - urllib2 handler for new http support
2 #
2 #
3 # Copyright 2005, 2006, 2007, 2008 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005, 2006, 2007, 2008 Matt Mackall <mpm@selenic.com>
4 # Copyright 2006, 2007 Alexis S. L. Carvalho <alexis@cecm.usp.br>
4 # Copyright 2006, 2007 Alexis S. L. Carvalho <alexis@cecm.usp.br>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
6 # Copyright 2011 Google, Inc.
6 # Copyright 2011 Google, Inc.
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 import logging
10 import logging
11 import socket
11 import socket
12 import urllib
12 import urllib
13 import urllib2
13 import urllib2
14 import os
14 import os
15
15
16 from mercurial import httpclient
16 from mercurial import httpclient
17 from mercurial import sslutil
17 from mercurial import sslutil
18 from mercurial import util
18 from mercurial import util
19 from mercurial.i18n import _
19 from mercurial.i18n import _
20
20
21 # moved here from url.py to avoid a cycle
21 # moved here from url.py to avoid a cycle
22 class httpsendfile(object):
22 class httpsendfile(object):
23 """This is a wrapper around the objects returned by python's "open".
23 """This is a wrapper around the objects returned by python's "open".
24
24
25 Its purpose is to send file-like objects via HTTP and, to do so, it
25 Its purpose is to send file-like objects via HTTP and, to do so, it
26 defines a __len__ attribute to feed the Content-Length header.
26 defines a __len__ attribute to feed the Content-Length header.
27 """
27 """
28
28
29 def __init__(self, ui, *args, **kwargs):
29 def __init__(self, ui, *args, **kwargs):
30 # We can't just "self._data = open(*args, **kwargs)" here because there
30 # We can't just "self._data = open(*args, **kwargs)" here because there
31 # is an "open" function defined in this module that shadows the global
31 # is an "open" function defined in this module that shadows the global
32 # one
32 # one
33 self.ui = ui
33 self.ui = ui
34 self._data = open(*args, **kwargs)
34 self._data = open(*args, **kwargs)
35 self.seek = self._data.seek
35 self.seek = self._data.seek
36 self.close = self._data.close
36 self.close = self._data.close
37 self.write = self._data.write
37 self.write = self._data.write
38 self._len = os.fstat(self._data.fileno()).st_size
38 self._len = os.fstat(self._data.fileno()).st_size
39 self._pos = 0
39 self._pos = 0
40 self._total = self._len / 1024 * 2
40 self._total = self._len / 1024 * 2
41
41
42 def read(self, *args, **kwargs):
42 def read(self, *args, **kwargs):
43 try:
43 try:
44 ret = self._data.read(*args, **kwargs)
44 ret = self._data.read(*args, **kwargs)
45 except EOFError:
45 except EOFError:
46 self.ui.progress(_('sending'), None)
46 self.ui.progress(_('sending'), None)
47 self._pos += len(ret)
47 self._pos += len(ret)
48 # We pass double the max for total because we currently have
48 # We pass double the max for total because we currently have
49 # to send the bundle twice in the case of a server that
49 # to send the bundle twice in the case of a server that
50 # requires authentication. Since we can't know until we try
50 # requires authentication. Since we can't know until we try
51 # once whether authentication will be required, just lie to
51 # once whether authentication will be required, just lie to
52 # the user and maybe the push succeeds suddenly at 50%.
52 # the user and maybe the push succeeds suddenly at 50%.
53 self.ui.progress(_('sending'), self._pos / 1024,
53 self.ui.progress(_('sending'), self._pos / 1024,
54 unit=_('kb'), total=self._total)
54 unit=_('kb'), total=self._total)
55 return ret
55 return ret
56
56
57 def __len__(self):
57 def __len__(self):
58 return self._len
58 return self._len
59
59
60 # moved here from url.py to avoid a cycle
60 # moved here from url.py to avoid a cycle
61 def readauthforuri(ui, uri):
61 def readauthforuri(ui, uri, user):
62 # Read configuration
62 # Read configuration
63 config = dict()
63 config = dict()
64 for key, val in ui.configitems('auth'):
64 for key, val in ui.configitems('auth'):
65 if '.' not in key:
65 if '.' not in key:
66 ui.warn(_("ignoring invalid [auth] key '%s'\n") % key)
66 ui.warn(_("ignoring invalid [auth] key '%s'\n") % key)
67 continue
67 continue
68 group, setting = key.rsplit('.', 1)
68 group, setting = key.rsplit('.', 1)
69 gdict = config.setdefault(group, dict())
69 gdict = config.setdefault(group, dict())
70 if setting in ('username', 'cert', 'key'):
70 if setting in ('username', 'cert', 'key'):
71 val = util.expandpath(val)
71 val = util.expandpath(val)
72 gdict[setting] = val
72 gdict[setting] = val
73
73
74 # Find the best match
74 # Find the best match
75 uri = util.url(uri)
76 user = uri.user
77 uri.user = uri.password = None
78 uri = str(uri)
79 scheme, hostpath = uri.split('://', 1)
75 scheme, hostpath = uri.split('://', 1)
80 bestuser = None
76 bestuser = None
81 bestlen = 0
77 bestlen = 0
82 bestauth = None
78 bestauth = None
83 for group, auth in config.iteritems():
79 for group, auth in config.iteritems():
84 if user and user != auth.get('username', user):
80 if user and user != auth.get('username', user):
85 # If a username was set in the URI, the entry username
81 # If a username was set in the URI, the entry username
86 # must either match it or be unset
82 # must either match it or be unset
87 continue
83 continue
88 prefix = auth.get('prefix')
84 prefix = auth.get('prefix')
89 if not prefix:
85 if not prefix:
90 continue
86 continue
91 p = prefix.split('://', 1)
87 p = prefix.split('://', 1)
92 if len(p) > 1:
88 if len(p) > 1:
93 schemes, prefix = [p[0]], p[1]
89 schemes, prefix = [p[0]], p[1]
94 else:
90 else:
95 schemes = (auth.get('schemes') or 'https').split()
91 schemes = (auth.get('schemes') or 'https').split()
96 if (prefix == '*' or hostpath.startswith(prefix)) and \
92 if (prefix == '*' or hostpath.startswith(prefix)) and \
97 (len(prefix) > bestlen or (len(prefix) == bestlen and \
93 (len(prefix) > bestlen or (len(prefix) == bestlen and \
98 not bestuser and 'username' in auth)) \
94 not bestuser and 'username' in auth)) \
99 and scheme in schemes:
95 and scheme in schemes:
100 bestlen = len(prefix)
96 bestlen = len(prefix)
101 bestauth = group, auth
97 bestauth = group, auth
102 bestuser = auth.get('username')
98 bestuser = auth.get('username')
103 if user and not bestuser:
99 if user and not bestuser:
104 auth['username'] = user
100 auth['username'] = user
105 return bestauth
101 return bestauth
106
102
107 # Mercurial (at least until we can remove the old codepath) requires
103 # Mercurial (at least until we can remove the old codepath) requires
108 # that the http response object be sufficiently file-like, so we
104 # that the http response object be sufficiently file-like, so we
109 # provide a close() method here.
105 # provide a close() method here.
110 class HTTPResponse(httpclient.HTTPResponse):
106 class HTTPResponse(httpclient.HTTPResponse):
111 def close(self):
107 def close(self):
112 pass
108 pass
113
109
114 class HTTPConnection(httpclient.HTTPConnection):
110 class HTTPConnection(httpclient.HTTPConnection):
115 response_class = HTTPResponse
111 response_class = HTTPResponse
116 def request(self, method, uri, body=None, headers={}):
112 def request(self, method, uri, body=None, headers={}):
117 if isinstance(body, httpsendfile):
113 if isinstance(body, httpsendfile):
118 body.seek(0)
114 body.seek(0)
119 httpclient.HTTPConnection.request(self, method, uri, body=body,
115 httpclient.HTTPConnection.request(self, method, uri, body=body,
120 headers=headers)
116 headers=headers)
121
117
122
118
123 _configuredlogging = False
119 _configuredlogging = False
124 LOGFMT = '%(levelname)s:%(name)s:%(lineno)d:%(message)s'
120 LOGFMT = '%(levelname)s:%(name)s:%(lineno)d:%(message)s'
125 # Subclass BOTH of these because otherwise urllib2 "helpfully"
121 # Subclass BOTH of these because otherwise urllib2 "helpfully"
126 # reinserts them since it notices we don't include any subclasses of
122 # reinserts them since it notices we don't include any subclasses of
127 # them.
123 # them.
128 class http2handler(urllib2.HTTPHandler, urllib2.HTTPSHandler):
124 class http2handler(urllib2.HTTPHandler, urllib2.HTTPSHandler):
129 def __init__(self, ui, pwmgr):
125 def __init__(self, ui, pwmgr):
130 global _configuredlogging
126 global _configuredlogging
131 urllib2.AbstractHTTPHandler.__init__(self)
127 urllib2.AbstractHTTPHandler.__init__(self)
132 self.ui = ui
128 self.ui = ui
133 self.pwmgr = pwmgr
129 self.pwmgr = pwmgr
134 self._connections = {}
130 self._connections = {}
135 loglevel = ui.config('ui', 'http2debuglevel', default=None)
131 loglevel = ui.config('ui', 'http2debuglevel', default=None)
136 if loglevel and not _configuredlogging:
132 if loglevel and not _configuredlogging:
137 _configuredlogging = True
133 _configuredlogging = True
138 logger = logging.getLogger('mercurial.httpclient')
134 logger = logging.getLogger('mercurial.httpclient')
139 logger.setLevel(getattr(logging, loglevel.upper()))
135 logger.setLevel(getattr(logging, loglevel.upper()))
140 handler = logging.StreamHandler()
136 handler = logging.StreamHandler()
141 handler.setFormatter(logging.Formatter(LOGFMT))
137 handler.setFormatter(logging.Formatter(LOGFMT))
142 logger.addHandler(handler)
138 logger.addHandler(handler)
143
139
144 def close_all(self):
140 def close_all(self):
145 """Close and remove all connection objects being kept for reuse."""
141 """Close and remove all connection objects being kept for reuse."""
146 for openconns in self._connections.values():
142 for openconns in self._connections.values():
147 for conn in openconns:
143 for conn in openconns:
148 conn.close()
144 conn.close()
149 self._connections = {}
145 self._connections = {}
150
146
151 # shamelessly borrowed from urllib2.AbstractHTTPHandler
147 # shamelessly borrowed from urllib2.AbstractHTTPHandler
152 def do_open(self, http_class, req, use_ssl):
148 def do_open(self, http_class, req, use_ssl):
153 """Return an addinfourl object for the request, using http_class.
149 """Return an addinfourl object for the request, using http_class.
154
150
155 http_class must implement the HTTPConnection API from httplib.
151 http_class must implement the HTTPConnection API from httplib.
156 The addinfourl return value is a file-like object. It also
152 The addinfourl return value is a file-like object. It also
157 has methods and attributes including:
153 has methods and attributes including:
158 - info(): return a mimetools.Message object for the headers
154 - info(): return a mimetools.Message object for the headers
159 - geturl(): return the original request URL
155 - geturl(): return the original request URL
160 - code: HTTP status code
156 - code: HTTP status code
161 """
157 """
162 # If using a proxy, the host returned by get_host() is
158 # If using a proxy, the host returned by get_host() is
163 # actually the proxy. On Python 2.6.1, the real destination
159 # actually the proxy. On Python 2.6.1, the real destination
164 # hostname is encoded in the URI in the urllib2 request
160 # hostname is encoded in the URI in the urllib2 request
165 # object. On Python 2.6.5, it's stored in the _tunnel_host
161 # object. On Python 2.6.5, it's stored in the _tunnel_host
166 # attribute which has no accessor.
162 # attribute which has no accessor.
167 tunhost = getattr(req, '_tunnel_host', None)
163 tunhost = getattr(req, '_tunnel_host', None)
168 host = req.get_host()
164 host = req.get_host()
169 if tunhost:
165 if tunhost:
170 proxyhost = host
166 proxyhost = host
171 host = tunhost
167 host = tunhost
172 elif req.has_proxy():
168 elif req.has_proxy():
173 proxyhost = req.get_host()
169 proxyhost = req.get_host()
174 host = req.get_selector().split('://', 1)[1].split('/', 1)[0]
170 host = req.get_selector().split('://', 1)[1].split('/', 1)[0]
175 else:
171 else:
176 proxyhost = None
172 proxyhost = None
177
173
178 if proxyhost:
174 if proxyhost:
179 if ':' in proxyhost:
175 if ':' in proxyhost:
180 # Note: this means we'll explode if we try and use an
176 # Note: this means we'll explode if we try and use an
181 # IPv6 http proxy. This isn't a regression, so we
177 # IPv6 http proxy. This isn't a regression, so we
182 # won't worry about it for now.
178 # won't worry about it for now.
183 proxyhost, proxyport = proxyhost.rsplit(':', 1)
179 proxyhost, proxyport = proxyhost.rsplit(':', 1)
184 else:
180 else:
185 proxyport = 3128 # squid default
181 proxyport = 3128 # squid default
186 proxy = (proxyhost, proxyport)
182 proxy = (proxyhost, proxyport)
187 else:
183 else:
188 proxy = None
184 proxy = None
189
185
190 if not host:
186 if not host:
191 raise urllib2.URLError('no host given')
187 raise urllib2.URLError('no host given')
192
188
193 connkey = use_ssl, host, proxy
189 connkey = use_ssl, host, proxy
194 allconns = self._connections.get(connkey, [])
190 allconns = self._connections.get(connkey, [])
195 conns = [c for c in allconns if not c.busy()]
191 conns = [c for c in allconns if not c.busy()]
196 if conns:
192 if conns:
197 h = conns[0]
193 h = conns[0]
198 else:
194 else:
199 if allconns:
195 if allconns:
200 self.ui.debug('all connections for %s busy, making a new '
196 self.ui.debug('all connections for %s busy, making a new '
201 'one\n' % host)
197 'one\n' % host)
202 timeout = None
198 timeout = None
203 if req.timeout is not socket._GLOBAL_DEFAULT_TIMEOUT:
199 if req.timeout is not socket._GLOBAL_DEFAULT_TIMEOUT:
204 timeout = req.timeout
200 timeout = req.timeout
205 h = http_class(host, timeout=timeout, proxy_hostport=proxy)
201 h = http_class(host, timeout=timeout, proxy_hostport=proxy)
206 self._connections.setdefault(connkey, []).append(h)
202 self._connections.setdefault(connkey, []).append(h)
207
203
208 headers = dict(req.headers)
204 headers = dict(req.headers)
209 headers.update(req.unredirected_hdrs)
205 headers.update(req.unredirected_hdrs)
210 headers = dict(
206 headers = dict(
211 (name.title(), val) for name, val in headers.items())
207 (name.title(), val) for name, val in headers.items())
212 try:
208 try:
213 path = req.get_selector()
209 path = req.get_selector()
214 if '://' in path:
210 if '://' in path:
215 path = path.split('://', 1)[1].split('/', 1)[1]
211 path = path.split('://', 1)[1].split('/', 1)[1]
216 if path[0] != '/':
212 if path[0] != '/':
217 path = '/' + path
213 path = '/' + path
218 h.request(req.get_method(), path, req.data, headers)
214 h.request(req.get_method(), path, req.data, headers)
219 r = h.getresponse()
215 r = h.getresponse()
220 except socket.error, err: # XXX what error?
216 except socket.error, err: # XXX what error?
221 raise urllib2.URLError(err)
217 raise urllib2.URLError(err)
222
218
223 # Pick apart the HTTPResponse object to get the addinfourl
219 # Pick apart the HTTPResponse object to get the addinfourl
224 # object initialized properly.
220 # object initialized properly.
225 r.recv = r.read
221 r.recv = r.read
226
222
227 resp = urllib.addinfourl(r, r.headers, req.get_full_url())
223 resp = urllib.addinfourl(r, r.headers, req.get_full_url())
228 resp.code = r.status
224 resp.code = r.status
229 resp.msg = r.reason
225 resp.msg = r.reason
230 return resp
226 return resp
231
227
232 # httplib always uses the given host/port as the socket connect
228 # httplib always uses the given host/port as the socket connect
233 # target, and then allows full URIs in the request path, which it
229 # target, and then allows full URIs in the request path, which it
234 # then observes and treats as a signal to do proxying instead.
230 # then observes and treats as a signal to do proxying instead.
235 def http_open(self, req):
231 def http_open(self, req):
236 if req.get_full_url().startswith('https'):
232 if req.get_full_url().startswith('https'):
237 return self.https_open(req)
233 return self.https_open(req)
238 return self.do_open(HTTPConnection, req, False)
234 return self.do_open(HTTPConnection, req, False)
239
235
240 def https_open(self, req):
236 def https_open(self, req):
241 res = readauthforuri(self.ui, req.get_full_url())
237 # req.get_full_url() does not contain credentials and we may
238 # need them to match the certificates.
239 url = req.get_full_url()
240 user, password = self.pwmgr.find_stored_password(url)
241 res = readauthforuri(self.ui, url, user)
242 if res:
242 if res:
243 group, auth = res
243 group, auth = res
244 self.auth = auth
244 self.auth = auth
245 self.ui.debug("using auth.%s.* for authentication\n" % group)
245 self.ui.debug("using auth.%s.* for authentication\n" % group)
246 else:
246 else:
247 self.auth = None
247 self.auth = None
248 return self.do_open(self._makesslconnection, req, True)
248 return self.do_open(self._makesslconnection, req, True)
249
249
250 def _makesslconnection(self, host, port=443, *args, **kwargs):
250 def _makesslconnection(self, host, port=443, *args, **kwargs):
251 keyfile = None
251 keyfile = None
252 certfile = None
252 certfile = None
253
253
254 if args: # key_file
254 if args: # key_file
255 keyfile = args.pop(0)
255 keyfile = args.pop(0)
256 if args: # cert_file
256 if args: # cert_file
257 certfile = args.pop(0)
257 certfile = args.pop(0)
258
258
259 # if the user has specified different key/cert files in
259 # if the user has specified different key/cert files in
260 # hgrc, we prefer these
260 # hgrc, we prefer these
261 if self.auth and 'key' in self.auth and 'cert' in self.auth:
261 if self.auth and 'key' in self.auth and 'cert' in self.auth:
262 keyfile = self.auth['key']
262 keyfile = self.auth['key']
263 certfile = self.auth['cert']
263 certfile = self.auth['cert']
264
264
265 # let host port take precedence
265 # let host port take precedence
266 if ':' in host and '[' not in host or ']:' in host:
266 if ':' in host and '[' not in host or ']:' in host:
267 host, port = host.rsplit(':', 1)
267 host, port = host.rsplit(':', 1)
268 port = int(port)
268 port = int(port)
269 if '[' in host:
269 if '[' in host:
270 host = host[1:-1]
270 host = host[1:-1]
271
271
272 if keyfile:
272 if keyfile:
273 kwargs['keyfile'] = keyfile
273 kwargs['keyfile'] = keyfile
274 if certfile:
274 if certfile:
275 kwargs['certfile'] = certfile
275 kwargs['certfile'] = certfile
276
276
277 kwargs.update(sslutil.sslkwargs(self.ui, host))
277 kwargs.update(sslutil.sslkwargs(self.ui, host))
278
278
279 con = HTTPConnection(host, port, use_ssl=True,
279 con = HTTPConnection(host, port, use_ssl=True,
280 ssl_validator=sslutil.validator(self.ui, host),
280 ssl_validator=sslutil.validator(self.ui, host),
281 **kwargs)
281 **kwargs)
282 return con
282 return con
@@ -1,463 +1,471 b''
1 # url.py - HTTP handling for mercurial
1 # url.py - HTTP handling for mercurial
2 #
2 #
3 # Copyright 2005, 2006, 2007, 2008 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005, 2006, 2007, 2008 Matt Mackall <mpm@selenic.com>
4 # Copyright 2006, 2007 Alexis S. L. Carvalho <alexis@cecm.usp.br>
4 # Copyright 2006, 2007 Alexis S. L. Carvalho <alexis@cecm.usp.br>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
6 #
6 #
7 # This software may be used and distributed according to the terms of the
7 # This software may be used and distributed according to the terms of the
8 # GNU General Public License version 2 or any later version.
8 # GNU General Public License version 2 or any later version.
9
9
10 import urllib, urllib2, httplib, os, socket, cStringIO
10 import urllib, urllib2, httplib, os, socket, cStringIO
11 from i18n import _
11 from i18n import _
12 import keepalive, util, sslutil
12 import keepalive, util, sslutil
13 import httpconnection as httpconnectionmod
13 import httpconnection as httpconnectionmod
14
14
15 class passwordmgr(urllib2.HTTPPasswordMgrWithDefaultRealm):
15 class passwordmgr(urllib2.HTTPPasswordMgrWithDefaultRealm):
16 def __init__(self, ui):
16 def __init__(self, ui):
17 urllib2.HTTPPasswordMgrWithDefaultRealm.__init__(self)
17 urllib2.HTTPPasswordMgrWithDefaultRealm.__init__(self)
18 self.ui = ui
18 self.ui = ui
19
19
20 def find_user_password(self, realm, authuri):
20 def find_user_password(self, realm, authuri):
21 authinfo = urllib2.HTTPPasswordMgrWithDefaultRealm.find_user_password(
21 authinfo = urllib2.HTTPPasswordMgrWithDefaultRealm.find_user_password(
22 self, realm, authuri)
22 self, realm, authuri)
23 user, passwd = authinfo
23 user, passwd = authinfo
24 if user and passwd:
24 if user and passwd:
25 self._writedebug(user, passwd)
25 self._writedebug(user, passwd)
26 return (user, passwd)
26 return (user, passwd)
27
27
28 if not user or not passwd:
28 if not user or not passwd:
29 res = httpconnectionmod.readauthforuri(self.ui, authuri)
29 res = httpconnectionmod.readauthforuri(self.ui, authuri, user)
30 if res:
30 if res:
31 group, auth = res
31 group, auth = res
32 user, passwd = auth.get('username'), auth.get('password')
32 user, passwd = auth.get('username'), auth.get('password')
33 self.ui.debug("using auth.%s.* for authentication\n" % group)
33 self.ui.debug("using auth.%s.* for authentication\n" % group)
34 if not user or not passwd:
34 if not user or not passwd:
35 if not self.ui.interactive():
35 if not self.ui.interactive():
36 raise util.Abort(_('http authorization required'))
36 raise util.Abort(_('http authorization required'))
37
37
38 self.ui.write(_("http authorization required\n"))
38 self.ui.write(_("http authorization required\n"))
39 self.ui.write(_("realm: %s\n") % realm)
39 self.ui.write(_("realm: %s\n") % realm)
40 if user:
40 if user:
41 self.ui.write(_("user: %s\n") % user)
41 self.ui.write(_("user: %s\n") % user)
42 else:
42 else:
43 user = self.ui.prompt(_("user:"), default=None)
43 user = self.ui.prompt(_("user:"), default=None)
44
44
45 if not passwd:
45 if not passwd:
46 passwd = self.ui.getpass()
46 passwd = self.ui.getpass()
47
47
48 self.add_password(realm, authuri, user, passwd)
48 self.add_password(realm, authuri, user, passwd)
49 self._writedebug(user, passwd)
49 self._writedebug(user, passwd)
50 return (user, passwd)
50 return (user, passwd)
51
51
52 def _writedebug(self, user, passwd):
52 def _writedebug(self, user, passwd):
53 msg = _('http auth: user %s, password %s\n')
53 msg = _('http auth: user %s, password %s\n')
54 self.ui.debug(msg % (user, passwd and '*' * len(passwd) or 'not set'))
54 self.ui.debug(msg % (user, passwd and '*' * len(passwd) or 'not set'))
55
55
56 def find_stored_password(self, authuri):
57 return urllib2.HTTPPasswordMgrWithDefaultRealm.find_user_password(
58 self, None, authuri)
59
56 class proxyhandler(urllib2.ProxyHandler):
60 class proxyhandler(urllib2.ProxyHandler):
57 def __init__(self, ui):
61 def __init__(self, ui):
58 proxyurl = ui.config("http_proxy", "host") or os.getenv('http_proxy')
62 proxyurl = ui.config("http_proxy", "host") or os.getenv('http_proxy')
59 # XXX proxyauthinfo = None
63 # XXX proxyauthinfo = None
60
64
61 if proxyurl:
65 if proxyurl:
62 # proxy can be proper url or host[:port]
66 # proxy can be proper url or host[:port]
63 if not (proxyurl.startswith('http:') or
67 if not (proxyurl.startswith('http:') or
64 proxyurl.startswith('https:')):
68 proxyurl.startswith('https:')):
65 proxyurl = 'http://' + proxyurl + '/'
69 proxyurl = 'http://' + proxyurl + '/'
66 proxy = util.url(proxyurl)
70 proxy = util.url(proxyurl)
67 if not proxy.user:
71 if not proxy.user:
68 proxy.user = ui.config("http_proxy", "user")
72 proxy.user = ui.config("http_proxy", "user")
69 proxy.passwd = ui.config("http_proxy", "passwd")
73 proxy.passwd = ui.config("http_proxy", "passwd")
70
74
71 # see if we should use a proxy for this url
75 # see if we should use a proxy for this url
72 no_list = ["localhost", "127.0.0.1"]
76 no_list = ["localhost", "127.0.0.1"]
73 no_list.extend([p.lower() for
77 no_list.extend([p.lower() for
74 p in ui.configlist("http_proxy", "no")])
78 p in ui.configlist("http_proxy", "no")])
75 no_list.extend([p.strip().lower() for
79 no_list.extend([p.strip().lower() for
76 p in os.getenv("no_proxy", '').split(',')
80 p in os.getenv("no_proxy", '').split(',')
77 if p.strip()])
81 if p.strip()])
78 # "http_proxy.always" config is for running tests on localhost
82 # "http_proxy.always" config is for running tests on localhost
79 if ui.configbool("http_proxy", "always"):
83 if ui.configbool("http_proxy", "always"):
80 self.no_list = []
84 self.no_list = []
81 else:
85 else:
82 self.no_list = no_list
86 self.no_list = no_list
83
87
84 proxyurl = str(proxy)
88 proxyurl = str(proxy)
85 proxies = {'http': proxyurl, 'https': proxyurl}
89 proxies = {'http': proxyurl, 'https': proxyurl}
86 ui.debug('proxying through http://%s:%s\n' %
90 ui.debug('proxying through http://%s:%s\n' %
87 (proxy.host, proxy.port))
91 (proxy.host, proxy.port))
88 else:
92 else:
89 proxies = {}
93 proxies = {}
90
94
91 # urllib2 takes proxy values from the environment and those
95 # urllib2 takes proxy values from the environment and those
92 # will take precedence if found, so drop them
96 # will take precedence if found, so drop them
93 for env in ["HTTP_PROXY", "http_proxy", "no_proxy"]:
97 for env in ["HTTP_PROXY", "http_proxy", "no_proxy"]:
94 try:
98 try:
95 if env in os.environ:
99 if env in os.environ:
96 del os.environ[env]
100 del os.environ[env]
97 except OSError:
101 except OSError:
98 pass
102 pass
99
103
100 urllib2.ProxyHandler.__init__(self, proxies)
104 urllib2.ProxyHandler.__init__(self, proxies)
101 self.ui = ui
105 self.ui = ui
102
106
103 def proxy_open(self, req, proxy, type_):
107 def proxy_open(self, req, proxy, type_):
104 host = req.get_host().split(':')[0]
108 host = req.get_host().split(':')[0]
105 if host in self.no_list:
109 if host in self.no_list:
106 return None
110 return None
107
111
108 # work around a bug in Python < 2.4.2
112 # work around a bug in Python < 2.4.2
109 # (it leaves a "\n" at the end of Proxy-authorization headers)
113 # (it leaves a "\n" at the end of Proxy-authorization headers)
110 baseclass = req.__class__
114 baseclass = req.__class__
111 class _request(baseclass):
115 class _request(baseclass):
112 def add_header(self, key, val):
116 def add_header(self, key, val):
113 if key.lower() == 'proxy-authorization':
117 if key.lower() == 'proxy-authorization':
114 val = val.strip()
118 val = val.strip()
115 return baseclass.add_header(self, key, val)
119 return baseclass.add_header(self, key, val)
116 req.__class__ = _request
120 req.__class__ = _request
117
121
118 return urllib2.ProxyHandler.proxy_open(self, req, proxy, type_)
122 return urllib2.ProxyHandler.proxy_open(self, req, proxy, type_)
119
123
120 def _gen_sendfile(orgsend):
124 def _gen_sendfile(orgsend):
121 def _sendfile(self, data):
125 def _sendfile(self, data):
122 # send a file
126 # send a file
123 if isinstance(data, httpconnectionmod.httpsendfile):
127 if isinstance(data, httpconnectionmod.httpsendfile):
124 # if auth required, some data sent twice, so rewind here
128 # if auth required, some data sent twice, so rewind here
125 data.seek(0)
129 data.seek(0)
126 for chunk in util.filechunkiter(data):
130 for chunk in util.filechunkiter(data):
127 orgsend(self, chunk)
131 orgsend(self, chunk)
128 else:
132 else:
129 orgsend(self, data)
133 orgsend(self, data)
130 return _sendfile
134 return _sendfile
131
135
132 has_https = util.safehasattr(urllib2, 'HTTPSHandler')
136 has_https = util.safehasattr(urllib2, 'HTTPSHandler')
133 if has_https:
137 if has_https:
134 try:
138 try:
135 _create_connection = socket.create_connection
139 _create_connection = socket.create_connection
136 except AttributeError:
140 except AttributeError:
137 _GLOBAL_DEFAULT_TIMEOUT = object()
141 _GLOBAL_DEFAULT_TIMEOUT = object()
138
142
139 def _create_connection(address, timeout=_GLOBAL_DEFAULT_TIMEOUT,
143 def _create_connection(address, timeout=_GLOBAL_DEFAULT_TIMEOUT,
140 source_address=None):
144 source_address=None):
141 # lifted from Python 2.6
145 # lifted from Python 2.6
142
146
143 msg = "getaddrinfo returns an empty list"
147 msg = "getaddrinfo returns an empty list"
144 host, port = address
148 host, port = address
145 for res in socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM):
149 for res in socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM):
146 af, socktype, proto, canonname, sa = res
150 af, socktype, proto, canonname, sa = res
147 sock = None
151 sock = None
148 try:
152 try:
149 sock = socket.socket(af, socktype, proto)
153 sock = socket.socket(af, socktype, proto)
150 if timeout is not _GLOBAL_DEFAULT_TIMEOUT:
154 if timeout is not _GLOBAL_DEFAULT_TIMEOUT:
151 sock.settimeout(timeout)
155 sock.settimeout(timeout)
152 if source_address:
156 if source_address:
153 sock.bind(source_address)
157 sock.bind(source_address)
154 sock.connect(sa)
158 sock.connect(sa)
155 return sock
159 return sock
156
160
157 except socket.error, msg:
161 except socket.error, msg:
158 if sock is not None:
162 if sock is not None:
159 sock.close()
163 sock.close()
160
164
161 raise socket.error, msg
165 raise socket.error, msg
162
166
163 class httpconnection(keepalive.HTTPConnection):
167 class httpconnection(keepalive.HTTPConnection):
164 # must be able to send big bundle as stream.
168 # must be able to send big bundle as stream.
165 send = _gen_sendfile(keepalive.HTTPConnection.send)
169 send = _gen_sendfile(keepalive.HTTPConnection.send)
166
170
167 def connect(self):
171 def connect(self):
168 if has_https and self.realhostport: # use CONNECT proxy
172 if has_https and self.realhostport: # use CONNECT proxy
169 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
173 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
170 self.sock.connect((self.host, self.port))
174 self.sock.connect((self.host, self.port))
171 if _generic_proxytunnel(self):
175 if _generic_proxytunnel(self):
172 # we do not support client x509 certificates
176 # we do not support client x509 certificates
173 self.sock = sslutil.ssl_wrap_socket(self.sock, None, None)
177 self.sock = sslutil.ssl_wrap_socket(self.sock, None, None)
174 else:
178 else:
175 keepalive.HTTPConnection.connect(self)
179 keepalive.HTTPConnection.connect(self)
176
180
177 def getresponse(self):
181 def getresponse(self):
178 proxyres = getattr(self, 'proxyres', None)
182 proxyres = getattr(self, 'proxyres', None)
179 if proxyres:
183 if proxyres:
180 if proxyres.will_close:
184 if proxyres.will_close:
181 self.close()
185 self.close()
182 self.proxyres = None
186 self.proxyres = None
183 return proxyres
187 return proxyres
184 return keepalive.HTTPConnection.getresponse(self)
188 return keepalive.HTTPConnection.getresponse(self)
185
189
186 # general transaction handler to support different ways to handle
190 # general transaction handler to support different ways to handle
187 # HTTPS proxying before and after Python 2.6.3.
191 # HTTPS proxying before and after Python 2.6.3.
188 def _generic_start_transaction(handler, h, req):
192 def _generic_start_transaction(handler, h, req):
189 tunnel_host = getattr(req, '_tunnel_host', None)
193 tunnel_host = getattr(req, '_tunnel_host', None)
190 if tunnel_host:
194 if tunnel_host:
191 if tunnel_host[:7] not in ['http://', 'https:/']:
195 if tunnel_host[:7] not in ['http://', 'https:/']:
192 tunnel_host = 'https://' + tunnel_host
196 tunnel_host = 'https://' + tunnel_host
193 new_tunnel = True
197 new_tunnel = True
194 else:
198 else:
195 tunnel_host = req.get_selector()
199 tunnel_host = req.get_selector()
196 new_tunnel = False
200 new_tunnel = False
197
201
198 if new_tunnel or tunnel_host == req.get_full_url(): # has proxy
202 if new_tunnel or tunnel_host == req.get_full_url(): # has proxy
199 u = util.url(tunnel_host)
203 u = util.url(tunnel_host)
200 if new_tunnel or u.scheme == 'https': # only use CONNECT for HTTPS
204 if new_tunnel or u.scheme == 'https': # only use CONNECT for HTTPS
201 h.realhostport = ':'.join([u.host, (u.port or '443')])
205 h.realhostport = ':'.join([u.host, (u.port or '443')])
202 h.headers = req.headers.copy()
206 h.headers = req.headers.copy()
203 h.headers.update(handler.parent.addheaders)
207 h.headers.update(handler.parent.addheaders)
204 return
208 return
205
209
206 h.realhostport = None
210 h.realhostport = None
207 h.headers = None
211 h.headers = None
208
212
209 def _generic_proxytunnel(self):
213 def _generic_proxytunnel(self):
210 proxyheaders = dict(
214 proxyheaders = dict(
211 [(x, self.headers[x]) for x in self.headers
215 [(x, self.headers[x]) for x in self.headers
212 if x.lower().startswith('proxy-')])
216 if x.lower().startswith('proxy-')])
213 self._set_hostport(self.host, self.port)
217 self._set_hostport(self.host, self.port)
214 self.send('CONNECT %s HTTP/1.0\r\n' % self.realhostport)
218 self.send('CONNECT %s HTTP/1.0\r\n' % self.realhostport)
215 for header in proxyheaders.iteritems():
219 for header in proxyheaders.iteritems():
216 self.send('%s: %s\r\n' % header)
220 self.send('%s: %s\r\n' % header)
217 self.send('\r\n')
221 self.send('\r\n')
218
222
219 # majority of the following code is duplicated from
223 # majority of the following code is duplicated from
220 # httplib.HTTPConnection as there are no adequate places to
224 # httplib.HTTPConnection as there are no adequate places to
221 # override functions to provide the needed functionality
225 # override functions to provide the needed functionality
222 res = self.response_class(self.sock,
226 res = self.response_class(self.sock,
223 strict=self.strict,
227 strict=self.strict,
224 method=self._method)
228 method=self._method)
225
229
226 while True:
230 while True:
227 version, status, reason = res._read_status()
231 version, status, reason = res._read_status()
228 if status != httplib.CONTINUE:
232 if status != httplib.CONTINUE:
229 break
233 break
230 while True:
234 while True:
231 skip = res.fp.readline().strip()
235 skip = res.fp.readline().strip()
232 if not skip:
236 if not skip:
233 break
237 break
234 res.status = status
238 res.status = status
235 res.reason = reason.strip()
239 res.reason = reason.strip()
236
240
237 if res.status == 200:
241 if res.status == 200:
238 while True:
242 while True:
239 line = res.fp.readline()
243 line = res.fp.readline()
240 if line == '\r\n':
244 if line == '\r\n':
241 break
245 break
242 return True
246 return True
243
247
244 if version == 'HTTP/1.0':
248 if version == 'HTTP/1.0':
245 res.version = 10
249 res.version = 10
246 elif version.startswith('HTTP/1.'):
250 elif version.startswith('HTTP/1.'):
247 res.version = 11
251 res.version = 11
248 elif version == 'HTTP/0.9':
252 elif version == 'HTTP/0.9':
249 res.version = 9
253 res.version = 9
250 else:
254 else:
251 raise httplib.UnknownProtocol(version)
255 raise httplib.UnknownProtocol(version)
252
256
253 if res.version == 9:
257 if res.version == 9:
254 res.length = None
258 res.length = None
255 res.chunked = 0
259 res.chunked = 0
256 res.will_close = 1
260 res.will_close = 1
257 res.msg = httplib.HTTPMessage(cStringIO.StringIO())
261 res.msg = httplib.HTTPMessage(cStringIO.StringIO())
258 return False
262 return False
259
263
260 res.msg = httplib.HTTPMessage(res.fp)
264 res.msg = httplib.HTTPMessage(res.fp)
261 res.msg.fp = None
265 res.msg.fp = None
262
266
263 # are we using the chunked-style of transfer encoding?
267 # are we using the chunked-style of transfer encoding?
264 trenc = res.msg.getheader('transfer-encoding')
268 trenc = res.msg.getheader('transfer-encoding')
265 if trenc and trenc.lower() == "chunked":
269 if trenc and trenc.lower() == "chunked":
266 res.chunked = 1
270 res.chunked = 1
267 res.chunk_left = None
271 res.chunk_left = None
268 else:
272 else:
269 res.chunked = 0
273 res.chunked = 0
270
274
271 # will the connection close at the end of the response?
275 # will the connection close at the end of the response?
272 res.will_close = res._check_close()
276 res.will_close = res._check_close()
273
277
274 # do we have a Content-Length?
278 # do we have a Content-Length?
275 # NOTE: RFC 2616, S4.4, #3 says we ignore this if tr_enc is "chunked"
279 # NOTE: RFC 2616, S4.4, #3 says we ignore this if tr_enc is "chunked"
276 length = res.msg.getheader('content-length')
280 length = res.msg.getheader('content-length')
277 if length and not res.chunked:
281 if length and not res.chunked:
278 try:
282 try:
279 res.length = int(length)
283 res.length = int(length)
280 except ValueError:
284 except ValueError:
281 res.length = None
285 res.length = None
282 else:
286 else:
283 if res.length < 0: # ignore nonsensical negative lengths
287 if res.length < 0: # ignore nonsensical negative lengths
284 res.length = None
288 res.length = None
285 else:
289 else:
286 res.length = None
290 res.length = None
287
291
288 # does the body have a fixed length? (of zero)
292 # does the body have a fixed length? (of zero)
289 if (status == httplib.NO_CONTENT or status == httplib.NOT_MODIFIED or
293 if (status == httplib.NO_CONTENT or status == httplib.NOT_MODIFIED or
290 100 <= status < 200 or # 1xx codes
294 100 <= status < 200 or # 1xx codes
291 res._method == 'HEAD'):
295 res._method == 'HEAD'):
292 res.length = 0
296 res.length = 0
293
297
294 # if the connection remains open, and we aren't using chunked, and
298 # if the connection remains open, and we aren't using chunked, and
295 # a content-length was not provided, then assume that the connection
299 # a content-length was not provided, then assume that the connection
296 # WILL close.
300 # WILL close.
297 if (not res.will_close and
301 if (not res.will_close and
298 not res.chunked and
302 not res.chunked and
299 res.length is None):
303 res.length is None):
300 res.will_close = 1
304 res.will_close = 1
301
305
302 self.proxyres = res
306 self.proxyres = res
303
307
304 return False
308 return False
305
309
306 class httphandler(keepalive.HTTPHandler):
310 class httphandler(keepalive.HTTPHandler):
307 def http_open(self, req):
311 def http_open(self, req):
308 return self.do_open(httpconnection, req)
312 return self.do_open(httpconnection, req)
309
313
310 def _start_transaction(self, h, req):
314 def _start_transaction(self, h, req):
311 _generic_start_transaction(self, h, req)
315 _generic_start_transaction(self, h, req)
312 return keepalive.HTTPHandler._start_transaction(self, h, req)
316 return keepalive.HTTPHandler._start_transaction(self, h, req)
313
317
314 if has_https:
318 if has_https:
315 class httpsconnection(httplib.HTTPSConnection):
319 class httpsconnection(httplib.HTTPSConnection):
316 response_class = keepalive.HTTPResponse
320 response_class = keepalive.HTTPResponse
317 # must be able to send big bundle as stream.
321 # must be able to send big bundle as stream.
318 send = _gen_sendfile(keepalive.safesend)
322 send = _gen_sendfile(keepalive.safesend)
319 getresponse = keepalive.wrapgetresponse(httplib.HTTPSConnection)
323 getresponse = keepalive.wrapgetresponse(httplib.HTTPSConnection)
320
324
321 def connect(self):
325 def connect(self):
322 self.sock = _create_connection((self.host, self.port))
326 self.sock = _create_connection((self.host, self.port))
323
327
324 host = self.host
328 host = self.host
325 if self.realhostport: # use CONNECT proxy
329 if self.realhostport: # use CONNECT proxy
326 _generic_proxytunnel(self)
330 _generic_proxytunnel(self)
327 host = self.realhostport.rsplit(':', 1)[0]
331 host = self.realhostport.rsplit(':', 1)[0]
328 self.sock = sslutil.ssl_wrap_socket(
332 self.sock = sslutil.ssl_wrap_socket(
329 self.sock, self.key_file, self.cert_file,
333 self.sock, self.key_file, self.cert_file,
330 **sslutil.sslkwargs(self.ui, host))
334 **sslutil.sslkwargs(self.ui, host))
331 sslutil.validator(self.ui, host)(self.sock)
335 sslutil.validator(self.ui, host)(self.sock)
332
336
333 class httpshandler(keepalive.KeepAliveHandler, urllib2.HTTPSHandler):
337 class httpshandler(keepalive.KeepAliveHandler, urllib2.HTTPSHandler):
334 def __init__(self, ui):
338 def __init__(self, ui):
335 keepalive.KeepAliveHandler.__init__(self)
339 keepalive.KeepAliveHandler.__init__(self)
336 urllib2.HTTPSHandler.__init__(self)
340 urllib2.HTTPSHandler.__init__(self)
337 self.ui = ui
341 self.ui = ui
338 self.pwmgr = passwordmgr(self.ui)
342 self.pwmgr = passwordmgr(self.ui)
339
343
340 def _start_transaction(self, h, req):
344 def _start_transaction(self, h, req):
341 _generic_start_transaction(self, h, req)
345 _generic_start_transaction(self, h, req)
342 return keepalive.KeepAliveHandler._start_transaction(self, h, req)
346 return keepalive.KeepAliveHandler._start_transaction(self, h, req)
343
347
344 def https_open(self, req):
348 def https_open(self, req):
345 res = httpconnectionmod.readauthforuri(self.ui, req.get_full_url())
349 # req.get_full_url() does not contain credentials and we may
350 # need them to match the certificates.
351 url = req.get_full_url()
352 user, password = self.pwmgr.find_stored_password(url)
353 res = httpconnectionmod.readauthforuri(self.ui, url, user)
346 if res:
354 if res:
347 group, auth = res
355 group, auth = res
348 self.auth = auth
356 self.auth = auth
349 self.ui.debug("using auth.%s.* for authentication\n" % group)
357 self.ui.debug("using auth.%s.* for authentication\n" % group)
350 else:
358 else:
351 self.auth = None
359 self.auth = None
352 return self.do_open(self._makeconnection, req)
360 return self.do_open(self._makeconnection, req)
353
361
354 def _makeconnection(self, host, port=None, *args, **kwargs):
362 def _makeconnection(self, host, port=None, *args, **kwargs):
355 keyfile = None
363 keyfile = None
356 certfile = None
364 certfile = None
357
365
358 if len(args) >= 1: # key_file
366 if len(args) >= 1: # key_file
359 keyfile = args[0]
367 keyfile = args[0]
360 if len(args) >= 2: # cert_file
368 if len(args) >= 2: # cert_file
361 certfile = args[1]
369 certfile = args[1]
362 args = args[2:]
370 args = args[2:]
363
371
364 # if the user has specified different key/cert files in
372 # if the user has specified different key/cert files in
365 # hgrc, we prefer these
373 # hgrc, we prefer these
366 if self.auth and 'key' in self.auth and 'cert' in self.auth:
374 if self.auth and 'key' in self.auth and 'cert' in self.auth:
367 keyfile = self.auth['key']
375 keyfile = self.auth['key']
368 certfile = self.auth['cert']
376 certfile = self.auth['cert']
369
377
370 conn = httpsconnection(host, port, keyfile, certfile, *args, **kwargs)
378 conn = httpsconnection(host, port, keyfile, certfile, *args, **kwargs)
371 conn.ui = self.ui
379 conn.ui = self.ui
372 return conn
380 return conn
373
381
374 class httpdigestauthhandler(urllib2.HTTPDigestAuthHandler):
382 class httpdigestauthhandler(urllib2.HTTPDigestAuthHandler):
375 def __init__(self, *args, **kwargs):
383 def __init__(self, *args, **kwargs):
376 urllib2.HTTPDigestAuthHandler.__init__(self, *args, **kwargs)
384 urllib2.HTTPDigestAuthHandler.__init__(self, *args, **kwargs)
377 self.retried_req = None
385 self.retried_req = None
378
386
379 def reset_retry_count(self):
387 def reset_retry_count(self):
380 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
388 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
381 # forever. We disable reset_retry_count completely and reset in
389 # forever. We disable reset_retry_count completely and reset in
382 # http_error_auth_reqed instead.
390 # http_error_auth_reqed instead.
383 pass
391 pass
384
392
385 def http_error_auth_reqed(self, auth_header, host, req, headers):
393 def http_error_auth_reqed(self, auth_header, host, req, headers):
386 # Reset the retry counter once for each request.
394 # Reset the retry counter once for each request.
387 if req is not self.retried_req:
395 if req is not self.retried_req:
388 self.retried_req = req
396 self.retried_req = req
389 self.retried = 0
397 self.retried = 0
390 # In python < 2.5 AbstractDigestAuthHandler raises a ValueError if
398 # In python < 2.5 AbstractDigestAuthHandler raises a ValueError if
391 # it doesn't know about the auth type requested. This can happen if
399 # it doesn't know about the auth type requested. This can happen if
392 # somebody is using BasicAuth and types a bad password.
400 # somebody is using BasicAuth and types a bad password.
393 try:
401 try:
394 return urllib2.HTTPDigestAuthHandler.http_error_auth_reqed(
402 return urllib2.HTTPDigestAuthHandler.http_error_auth_reqed(
395 self, auth_header, host, req, headers)
403 self, auth_header, host, req, headers)
396 except ValueError, inst:
404 except ValueError, inst:
397 arg = inst.args[0]
405 arg = inst.args[0]
398 if arg.startswith("AbstractDigestAuthHandler doesn't know "):
406 if arg.startswith("AbstractDigestAuthHandler doesn't know "):
399 return
407 return
400 raise
408 raise
401
409
402 class httpbasicauthhandler(urllib2.HTTPBasicAuthHandler):
410 class httpbasicauthhandler(urllib2.HTTPBasicAuthHandler):
403 def __init__(self, *args, **kwargs):
411 def __init__(self, *args, **kwargs):
404 urllib2.HTTPBasicAuthHandler.__init__(self, *args, **kwargs)
412 urllib2.HTTPBasicAuthHandler.__init__(self, *args, **kwargs)
405 self.retried_req = None
413 self.retried_req = None
406
414
407 def reset_retry_count(self):
415 def reset_retry_count(self):
408 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
416 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
409 # forever. We disable reset_retry_count completely and reset in
417 # forever. We disable reset_retry_count completely and reset in
410 # http_error_auth_reqed instead.
418 # http_error_auth_reqed instead.
411 pass
419 pass
412
420
413 def http_error_auth_reqed(self, auth_header, host, req, headers):
421 def http_error_auth_reqed(self, auth_header, host, req, headers):
414 # Reset the retry counter once for each request.
422 # Reset the retry counter once for each request.
415 if req is not self.retried_req:
423 if req is not self.retried_req:
416 self.retried_req = req
424 self.retried_req = req
417 self.retried = 0
425 self.retried = 0
418 return urllib2.HTTPBasicAuthHandler.http_error_auth_reqed(
426 return urllib2.HTTPBasicAuthHandler.http_error_auth_reqed(
419 self, auth_header, host, req, headers)
427 self, auth_header, host, req, headers)
420
428
421 handlerfuncs = []
429 handlerfuncs = []
422
430
423 def opener(ui, authinfo=None):
431 def opener(ui, authinfo=None):
424 '''
432 '''
425 construct an opener suitable for urllib2
433 construct an opener suitable for urllib2
426 authinfo will be added to the password manager
434 authinfo will be added to the password manager
427 '''
435 '''
428 if ui.configbool('ui', 'usehttp2', False):
436 if ui.configbool('ui', 'usehttp2', False):
429 handlers = [httpconnectionmod.http2handler(ui, passwordmgr(ui))]
437 handlers = [httpconnectionmod.http2handler(ui, passwordmgr(ui))]
430 else:
438 else:
431 handlers = [httphandler()]
439 handlers = [httphandler()]
432 if has_https:
440 if has_https:
433 handlers.append(httpshandler(ui))
441 handlers.append(httpshandler(ui))
434
442
435 handlers.append(proxyhandler(ui))
443 handlers.append(proxyhandler(ui))
436
444
437 passmgr = passwordmgr(ui)
445 passmgr = passwordmgr(ui)
438 if authinfo is not None:
446 if authinfo is not None:
439 passmgr.add_password(*authinfo)
447 passmgr.add_password(*authinfo)
440 user, passwd = authinfo[2:4]
448 user, passwd = authinfo[2:4]
441 ui.debug('http auth: user %s, password %s\n' %
449 ui.debug('http auth: user %s, password %s\n' %
442 (user, passwd and '*' * len(passwd) or 'not set'))
450 (user, passwd and '*' * len(passwd) or 'not set'))
443
451
444 handlers.extend((httpbasicauthhandler(passmgr),
452 handlers.extend((httpbasicauthhandler(passmgr),
445 httpdigestauthhandler(passmgr)))
453 httpdigestauthhandler(passmgr)))
446 handlers.extend([h(ui, passmgr) for h in handlerfuncs])
454 handlers.extend([h(ui, passmgr) for h in handlerfuncs])
447 opener = urllib2.build_opener(*handlers)
455 opener = urllib2.build_opener(*handlers)
448
456
449 # 1.0 here is the _protocol_ version
457 # 1.0 here is the _protocol_ version
450 opener.addheaders = [('User-agent', 'mercurial/proto-1.0')]
458 opener.addheaders = [('User-agent', 'mercurial/proto-1.0')]
451 opener.addheaders.append(('Accept', 'application/mercurial-0.1'))
459 opener.addheaders.append(('Accept', 'application/mercurial-0.1'))
452 return opener
460 return opener
453
461
454 def open(ui, url_, data=None):
462 def open(ui, url_, data=None):
455 u = util.url(url_)
463 u = util.url(url_)
456 if u.scheme:
464 if u.scheme:
457 u.scheme = u.scheme.lower()
465 u.scheme = u.scheme.lower()
458 url_, authinfo = u.authinfo()
466 url_, authinfo = u.authinfo()
459 else:
467 else:
460 path = util.normpath(os.path.abspath(url_))
468 path = util.normpath(os.path.abspath(url_))
461 url_ = 'file://' + urllib.pathname2url(path)
469 url_ = 'file://' + urllib.pathname2url(path)
462 authinfo = None
470 authinfo = None
463 return opener(ui, authinfo).open(url_, data)
471 return opener(ui, authinfo).open(url_, data)
@@ -1,1647 +1,1646 b''
1 # util.py - Mercurial utility functions and platform specfic implementations
1 # util.py - Mercurial utility functions and platform specfic implementations
2 #
2 #
3 # Copyright 2005 K. Thananchayan <thananck@yahoo.com>
3 # Copyright 2005 K. Thananchayan <thananck@yahoo.com>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
6 #
6 #
7 # This software may be used and distributed according to the terms of the
7 # This software may be used and distributed according to the terms of the
8 # GNU General Public License version 2 or any later version.
8 # GNU General Public License version 2 or any later version.
9
9
10 """Mercurial utility functions and platform specfic implementations.
10 """Mercurial utility functions and platform specfic implementations.
11
11
12 This contains helper routines that are independent of the SCM core and
12 This contains helper routines that are independent of the SCM core and
13 hide platform-specific details from the core.
13 hide platform-specific details from the core.
14 """
14 """
15
15
16 from i18n import _
16 from i18n import _
17 import error, osutil, encoding
17 import error, osutil, encoding
18 import errno, re, shutil, sys, tempfile, traceback
18 import errno, re, shutil, sys, tempfile, traceback
19 import os, time, calendar, textwrap, unicodedata, signal
19 import os, time, calendar, textwrap, unicodedata, signal
20 import imp, socket, urllib
20 import imp, socket, urllib
21
21
22 if os.name == 'nt':
22 if os.name == 'nt':
23 import windows as platform
23 import windows as platform
24 else:
24 else:
25 import posix as platform
25 import posix as platform
26
26
27 cachestat = platform.cachestat
27 cachestat = platform.cachestat
28 checkexec = platform.checkexec
28 checkexec = platform.checkexec
29 checklink = platform.checklink
29 checklink = platform.checklink
30 copymode = platform.copymode
30 copymode = platform.copymode
31 executablepath = platform.executablepath
31 executablepath = platform.executablepath
32 expandglobs = platform.expandglobs
32 expandglobs = platform.expandglobs
33 explainexit = platform.explainexit
33 explainexit = platform.explainexit
34 findexe = platform.findexe
34 findexe = platform.findexe
35 gethgcmd = platform.gethgcmd
35 gethgcmd = platform.gethgcmd
36 getuser = platform.getuser
36 getuser = platform.getuser
37 groupmembers = platform.groupmembers
37 groupmembers = platform.groupmembers
38 groupname = platform.groupname
38 groupname = platform.groupname
39 hidewindow = platform.hidewindow
39 hidewindow = platform.hidewindow
40 isexec = platform.isexec
40 isexec = platform.isexec
41 isowner = platform.isowner
41 isowner = platform.isowner
42 localpath = platform.localpath
42 localpath = platform.localpath
43 lookupreg = platform.lookupreg
43 lookupreg = platform.lookupreg
44 makedir = platform.makedir
44 makedir = platform.makedir
45 nlinks = platform.nlinks
45 nlinks = platform.nlinks
46 normpath = platform.normpath
46 normpath = platform.normpath
47 nulldev = platform.nulldev
47 nulldev = platform.nulldev
48 openhardlinks = platform.openhardlinks
48 openhardlinks = platform.openhardlinks
49 oslink = platform.oslink
49 oslink = platform.oslink
50 parsepatchoutput = platform.parsepatchoutput
50 parsepatchoutput = platform.parsepatchoutput
51 pconvert = platform.pconvert
51 pconvert = platform.pconvert
52 popen = platform.popen
52 popen = platform.popen
53 posixfile = platform.posixfile
53 posixfile = platform.posixfile
54 quotecommand = platform.quotecommand
54 quotecommand = platform.quotecommand
55 realpath = platform.realpath
55 realpath = platform.realpath
56 rename = platform.rename
56 rename = platform.rename
57 samedevice = platform.samedevice
57 samedevice = platform.samedevice
58 samefile = platform.samefile
58 samefile = platform.samefile
59 samestat = platform.samestat
59 samestat = platform.samestat
60 setbinary = platform.setbinary
60 setbinary = platform.setbinary
61 setflags = platform.setflags
61 setflags = platform.setflags
62 setsignalhandler = platform.setsignalhandler
62 setsignalhandler = platform.setsignalhandler
63 shellquote = platform.shellquote
63 shellquote = platform.shellquote
64 spawndetached = platform.spawndetached
64 spawndetached = platform.spawndetached
65 sshargs = platform.sshargs
65 sshargs = platform.sshargs
66 statfiles = platform.statfiles
66 statfiles = platform.statfiles
67 termwidth = platform.termwidth
67 termwidth = platform.termwidth
68 testpid = platform.testpid
68 testpid = platform.testpid
69 umask = platform.umask
69 umask = platform.umask
70 unlink = platform.unlink
70 unlink = platform.unlink
71 unlinkpath = platform.unlinkpath
71 unlinkpath = platform.unlinkpath
72 username = platform.username
72 username = platform.username
73
73
74 # Python compatibility
74 # Python compatibility
75
75
76 def sha1(s):
76 def sha1(s):
77 return _fastsha1(s)
77 return _fastsha1(s)
78
78
79 _notset = object()
79 _notset = object()
80 def safehasattr(thing, attr):
80 def safehasattr(thing, attr):
81 return getattr(thing, attr, _notset) is not _notset
81 return getattr(thing, attr, _notset) is not _notset
82
82
83 def _fastsha1(s):
83 def _fastsha1(s):
84 # This function will import sha1 from hashlib or sha (whichever is
84 # This function will import sha1 from hashlib or sha (whichever is
85 # available) and overwrite itself with it on the first call.
85 # available) and overwrite itself with it on the first call.
86 # Subsequent calls will go directly to the imported function.
86 # Subsequent calls will go directly to the imported function.
87 if sys.version_info >= (2, 5):
87 if sys.version_info >= (2, 5):
88 from hashlib import sha1 as _sha1
88 from hashlib import sha1 as _sha1
89 else:
89 else:
90 from sha import sha as _sha1
90 from sha import sha as _sha1
91 global _fastsha1, sha1
91 global _fastsha1, sha1
92 _fastsha1 = sha1 = _sha1
92 _fastsha1 = sha1 = _sha1
93 return _sha1(s)
93 return _sha1(s)
94
94
95 import __builtin__
95 import __builtin__
96
96
97 if sys.version_info[0] < 3:
97 if sys.version_info[0] < 3:
98 def fakebuffer(sliceable, offset=0):
98 def fakebuffer(sliceable, offset=0):
99 return sliceable[offset:]
99 return sliceable[offset:]
100 else:
100 else:
101 def fakebuffer(sliceable, offset=0):
101 def fakebuffer(sliceable, offset=0):
102 return memoryview(sliceable)[offset:]
102 return memoryview(sliceable)[offset:]
103 try:
103 try:
104 buffer
104 buffer
105 except NameError:
105 except NameError:
106 __builtin__.buffer = fakebuffer
106 __builtin__.buffer = fakebuffer
107
107
108 import subprocess
108 import subprocess
109 closefds = os.name == 'posix'
109 closefds = os.name == 'posix'
110
110
111 def popen2(cmd, env=None, newlines=False):
111 def popen2(cmd, env=None, newlines=False):
112 # Setting bufsize to -1 lets the system decide the buffer size.
112 # Setting bufsize to -1 lets the system decide the buffer size.
113 # The default for bufsize is 0, meaning unbuffered. This leads to
113 # The default for bufsize is 0, meaning unbuffered. This leads to
114 # poor performance on Mac OS X: http://bugs.python.org/issue4194
114 # poor performance on Mac OS X: http://bugs.python.org/issue4194
115 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
115 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
116 close_fds=closefds,
116 close_fds=closefds,
117 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
117 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
118 universal_newlines=newlines,
118 universal_newlines=newlines,
119 env=env)
119 env=env)
120 return p.stdin, p.stdout
120 return p.stdin, p.stdout
121
121
122 def popen3(cmd, env=None, newlines=False):
122 def popen3(cmd, env=None, newlines=False):
123 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
123 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
124 close_fds=closefds,
124 close_fds=closefds,
125 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
125 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
126 stderr=subprocess.PIPE,
126 stderr=subprocess.PIPE,
127 universal_newlines=newlines,
127 universal_newlines=newlines,
128 env=env)
128 env=env)
129 return p.stdin, p.stdout, p.stderr
129 return p.stdin, p.stdout, p.stderr
130
130
131 def version():
131 def version():
132 """Return version information if available."""
132 """Return version information if available."""
133 try:
133 try:
134 import __version__
134 import __version__
135 return __version__.version
135 return __version__.version
136 except ImportError:
136 except ImportError:
137 return 'unknown'
137 return 'unknown'
138
138
139 # used by parsedate
139 # used by parsedate
140 defaultdateformats = (
140 defaultdateformats = (
141 '%Y-%m-%d %H:%M:%S',
141 '%Y-%m-%d %H:%M:%S',
142 '%Y-%m-%d %I:%M:%S%p',
142 '%Y-%m-%d %I:%M:%S%p',
143 '%Y-%m-%d %H:%M',
143 '%Y-%m-%d %H:%M',
144 '%Y-%m-%d %I:%M%p',
144 '%Y-%m-%d %I:%M%p',
145 '%Y-%m-%d',
145 '%Y-%m-%d',
146 '%m-%d',
146 '%m-%d',
147 '%m/%d',
147 '%m/%d',
148 '%m/%d/%y',
148 '%m/%d/%y',
149 '%m/%d/%Y',
149 '%m/%d/%Y',
150 '%a %b %d %H:%M:%S %Y',
150 '%a %b %d %H:%M:%S %Y',
151 '%a %b %d %I:%M:%S%p %Y',
151 '%a %b %d %I:%M:%S%p %Y',
152 '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
152 '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
153 '%b %d %H:%M:%S %Y',
153 '%b %d %H:%M:%S %Y',
154 '%b %d %I:%M:%S%p %Y',
154 '%b %d %I:%M:%S%p %Y',
155 '%b %d %H:%M:%S',
155 '%b %d %H:%M:%S',
156 '%b %d %I:%M:%S%p',
156 '%b %d %I:%M:%S%p',
157 '%b %d %H:%M',
157 '%b %d %H:%M',
158 '%b %d %I:%M%p',
158 '%b %d %I:%M%p',
159 '%b %d %Y',
159 '%b %d %Y',
160 '%b %d',
160 '%b %d',
161 '%H:%M:%S',
161 '%H:%M:%S',
162 '%I:%M:%S%p',
162 '%I:%M:%S%p',
163 '%H:%M',
163 '%H:%M',
164 '%I:%M%p',
164 '%I:%M%p',
165 )
165 )
166
166
167 extendeddateformats = defaultdateformats + (
167 extendeddateformats = defaultdateformats + (
168 "%Y",
168 "%Y",
169 "%Y-%m",
169 "%Y-%m",
170 "%b",
170 "%b",
171 "%b %Y",
171 "%b %Y",
172 )
172 )
173
173
174 def cachefunc(func):
174 def cachefunc(func):
175 '''cache the result of function calls'''
175 '''cache the result of function calls'''
176 # XXX doesn't handle keywords args
176 # XXX doesn't handle keywords args
177 cache = {}
177 cache = {}
178 if func.func_code.co_argcount == 1:
178 if func.func_code.co_argcount == 1:
179 # we gain a small amount of time because
179 # we gain a small amount of time because
180 # we don't need to pack/unpack the list
180 # we don't need to pack/unpack the list
181 def f(arg):
181 def f(arg):
182 if arg not in cache:
182 if arg not in cache:
183 cache[arg] = func(arg)
183 cache[arg] = func(arg)
184 return cache[arg]
184 return cache[arg]
185 else:
185 else:
186 def f(*args):
186 def f(*args):
187 if args not in cache:
187 if args not in cache:
188 cache[args] = func(*args)
188 cache[args] = func(*args)
189 return cache[args]
189 return cache[args]
190
190
191 return f
191 return f
192
192
193 def lrucachefunc(func):
193 def lrucachefunc(func):
194 '''cache most recent results of function calls'''
194 '''cache most recent results of function calls'''
195 cache = {}
195 cache = {}
196 order = []
196 order = []
197 if func.func_code.co_argcount == 1:
197 if func.func_code.co_argcount == 1:
198 def f(arg):
198 def f(arg):
199 if arg not in cache:
199 if arg not in cache:
200 if len(cache) > 20:
200 if len(cache) > 20:
201 del cache[order.pop(0)]
201 del cache[order.pop(0)]
202 cache[arg] = func(arg)
202 cache[arg] = func(arg)
203 else:
203 else:
204 order.remove(arg)
204 order.remove(arg)
205 order.append(arg)
205 order.append(arg)
206 return cache[arg]
206 return cache[arg]
207 else:
207 else:
208 def f(*args):
208 def f(*args):
209 if args not in cache:
209 if args not in cache:
210 if len(cache) > 20:
210 if len(cache) > 20:
211 del cache[order.pop(0)]
211 del cache[order.pop(0)]
212 cache[args] = func(*args)
212 cache[args] = func(*args)
213 else:
213 else:
214 order.remove(args)
214 order.remove(args)
215 order.append(args)
215 order.append(args)
216 return cache[args]
216 return cache[args]
217
217
218 return f
218 return f
219
219
220 class propertycache(object):
220 class propertycache(object):
221 def __init__(self, func):
221 def __init__(self, func):
222 self.func = func
222 self.func = func
223 self.name = func.__name__
223 self.name = func.__name__
224 def __get__(self, obj, type=None):
224 def __get__(self, obj, type=None):
225 result = self.func(obj)
225 result = self.func(obj)
226 setattr(obj, self.name, result)
226 setattr(obj, self.name, result)
227 return result
227 return result
228
228
229 def pipefilter(s, cmd):
229 def pipefilter(s, cmd):
230 '''filter string S through command CMD, returning its output'''
230 '''filter string S through command CMD, returning its output'''
231 p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
231 p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
232 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
232 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
233 pout, perr = p.communicate(s)
233 pout, perr = p.communicate(s)
234 return pout
234 return pout
235
235
236 def tempfilter(s, cmd):
236 def tempfilter(s, cmd):
237 '''filter string S through a pair of temporary files with CMD.
237 '''filter string S through a pair of temporary files with CMD.
238 CMD is used as a template to create the real command to be run,
238 CMD is used as a template to create the real command to be run,
239 with the strings INFILE and OUTFILE replaced by the real names of
239 with the strings INFILE and OUTFILE replaced by the real names of
240 the temporary files generated.'''
240 the temporary files generated.'''
241 inname, outname = None, None
241 inname, outname = None, None
242 try:
242 try:
243 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
243 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
244 fp = os.fdopen(infd, 'wb')
244 fp = os.fdopen(infd, 'wb')
245 fp.write(s)
245 fp.write(s)
246 fp.close()
246 fp.close()
247 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
247 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
248 os.close(outfd)
248 os.close(outfd)
249 cmd = cmd.replace('INFILE', inname)
249 cmd = cmd.replace('INFILE', inname)
250 cmd = cmd.replace('OUTFILE', outname)
250 cmd = cmd.replace('OUTFILE', outname)
251 code = os.system(cmd)
251 code = os.system(cmd)
252 if sys.platform == 'OpenVMS' and code & 1:
252 if sys.platform == 'OpenVMS' and code & 1:
253 code = 0
253 code = 0
254 if code:
254 if code:
255 raise Abort(_("command '%s' failed: %s") %
255 raise Abort(_("command '%s' failed: %s") %
256 (cmd, explainexit(code)))
256 (cmd, explainexit(code)))
257 fp = open(outname, 'rb')
257 fp = open(outname, 'rb')
258 r = fp.read()
258 r = fp.read()
259 fp.close()
259 fp.close()
260 return r
260 return r
261 finally:
261 finally:
262 try:
262 try:
263 if inname:
263 if inname:
264 os.unlink(inname)
264 os.unlink(inname)
265 except OSError:
265 except OSError:
266 pass
266 pass
267 try:
267 try:
268 if outname:
268 if outname:
269 os.unlink(outname)
269 os.unlink(outname)
270 except OSError:
270 except OSError:
271 pass
271 pass
272
272
273 filtertable = {
273 filtertable = {
274 'tempfile:': tempfilter,
274 'tempfile:': tempfilter,
275 'pipe:': pipefilter,
275 'pipe:': pipefilter,
276 }
276 }
277
277
278 def filter(s, cmd):
278 def filter(s, cmd):
279 "filter a string through a command that transforms its input to its output"
279 "filter a string through a command that transforms its input to its output"
280 for name, fn in filtertable.iteritems():
280 for name, fn in filtertable.iteritems():
281 if cmd.startswith(name):
281 if cmd.startswith(name):
282 return fn(s, cmd[len(name):].lstrip())
282 return fn(s, cmd[len(name):].lstrip())
283 return pipefilter(s, cmd)
283 return pipefilter(s, cmd)
284
284
285 def binary(s):
285 def binary(s):
286 """return true if a string is binary data"""
286 """return true if a string is binary data"""
287 return bool(s and '\0' in s)
287 return bool(s and '\0' in s)
288
288
289 def increasingchunks(source, min=1024, max=65536):
289 def increasingchunks(source, min=1024, max=65536):
290 '''return no less than min bytes per chunk while data remains,
290 '''return no less than min bytes per chunk while data remains,
291 doubling min after each chunk until it reaches max'''
291 doubling min after each chunk until it reaches max'''
292 def log2(x):
292 def log2(x):
293 if not x:
293 if not x:
294 return 0
294 return 0
295 i = 0
295 i = 0
296 while x:
296 while x:
297 x >>= 1
297 x >>= 1
298 i += 1
298 i += 1
299 return i - 1
299 return i - 1
300
300
301 buf = []
301 buf = []
302 blen = 0
302 blen = 0
303 for chunk in source:
303 for chunk in source:
304 buf.append(chunk)
304 buf.append(chunk)
305 blen += len(chunk)
305 blen += len(chunk)
306 if blen >= min:
306 if blen >= min:
307 if min < max:
307 if min < max:
308 min = min << 1
308 min = min << 1
309 nmin = 1 << log2(blen)
309 nmin = 1 << log2(blen)
310 if nmin > min:
310 if nmin > min:
311 min = nmin
311 min = nmin
312 if min > max:
312 if min > max:
313 min = max
313 min = max
314 yield ''.join(buf)
314 yield ''.join(buf)
315 blen = 0
315 blen = 0
316 buf = []
316 buf = []
317 if buf:
317 if buf:
318 yield ''.join(buf)
318 yield ''.join(buf)
319
319
320 Abort = error.Abort
320 Abort = error.Abort
321
321
322 def always(fn):
322 def always(fn):
323 return True
323 return True
324
324
325 def never(fn):
325 def never(fn):
326 return False
326 return False
327
327
328 def pathto(root, n1, n2):
328 def pathto(root, n1, n2):
329 '''return the relative path from one place to another.
329 '''return the relative path from one place to another.
330 root should use os.sep to separate directories
330 root should use os.sep to separate directories
331 n1 should use os.sep to separate directories
331 n1 should use os.sep to separate directories
332 n2 should use "/" to separate directories
332 n2 should use "/" to separate directories
333 returns an os.sep-separated path.
333 returns an os.sep-separated path.
334
334
335 If n1 is a relative path, it's assumed it's
335 If n1 is a relative path, it's assumed it's
336 relative to root.
336 relative to root.
337 n2 should always be relative to root.
337 n2 should always be relative to root.
338 '''
338 '''
339 if not n1:
339 if not n1:
340 return localpath(n2)
340 return localpath(n2)
341 if os.path.isabs(n1):
341 if os.path.isabs(n1):
342 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
342 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
343 return os.path.join(root, localpath(n2))
343 return os.path.join(root, localpath(n2))
344 n2 = '/'.join((pconvert(root), n2))
344 n2 = '/'.join((pconvert(root), n2))
345 a, b = splitpath(n1), n2.split('/')
345 a, b = splitpath(n1), n2.split('/')
346 a.reverse()
346 a.reverse()
347 b.reverse()
347 b.reverse()
348 while a and b and a[-1] == b[-1]:
348 while a and b and a[-1] == b[-1]:
349 a.pop()
349 a.pop()
350 b.pop()
350 b.pop()
351 b.reverse()
351 b.reverse()
352 return os.sep.join((['..'] * len(a)) + b) or '.'
352 return os.sep.join((['..'] * len(a)) + b) or '.'
353
353
354 _hgexecutable = None
354 _hgexecutable = None
355
355
356 def mainfrozen():
356 def mainfrozen():
357 """return True if we are a frozen executable.
357 """return True if we are a frozen executable.
358
358
359 The code supports py2exe (most common, Windows only) and tools/freeze
359 The code supports py2exe (most common, Windows only) and tools/freeze
360 (portable, not much used).
360 (portable, not much used).
361 """
361 """
362 return (safehasattr(sys, "frozen") or # new py2exe
362 return (safehasattr(sys, "frozen") or # new py2exe
363 safehasattr(sys, "importers") or # old py2exe
363 safehasattr(sys, "importers") or # old py2exe
364 imp.is_frozen("__main__")) # tools/freeze
364 imp.is_frozen("__main__")) # tools/freeze
365
365
366 def hgexecutable():
366 def hgexecutable():
367 """return location of the 'hg' executable.
367 """return location of the 'hg' executable.
368
368
369 Defaults to $HG or 'hg' in the search path.
369 Defaults to $HG or 'hg' in the search path.
370 """
370 """
371 if _hgexecutable is None:
371 if _hgexecutable is None:
372 hg = os.environ.get('HG')
372 hg = os.environ.get('HG')
373 if hg:
373 if hg:
374 _sethgexecutable(hg)
374 _sethgexecutable(hg)
375 elif mainfrozen():
375 elif mainfrozen():
376 _sethgexecutable(sys.executable)
376 _sethgexecutable(sys.executable)
377 else:
377 else:
378 exe = findexe('hg') or os.path.basename(sys.argv[0])
378 exe = findexe('hg') or os.path.basename(sys.argv[0])
379 _sethgexecutable(exe)
379 _sethgexecutable(exe)
380 return _hgexecutable
380 return _hgexecutable
381
381
382 def _sethgexecutable(path):
382 def _sethgexecutable(path):
383 """set location of the 'hg' executable"""
383 """set location of the 'hg' executable"""
384 global _hgexecutable
384 global _hgexecutable
385 _hgexecutable = path
385 _hgexecutable = path
386
386
387 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None, out=None):
387 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None, out=None):
388 '''enhanced shell command execution.
388 '''enhanced shell command execution.
389 run with environment maybe modified, maybe in different dir.
389 run with environment maybe modified, maybe in different dir.
390
390
391 if command fails and onerr is None, return status. if ui object,
391 if command fails and onerr is None, return status. if ui object,
392 print error message and return status, else raise onerr object as
392 print error message and return status, else raise onerr object as
393 exception.
393 exception.
394
394
395 if out is specified, it is assumed to be a file-like object that has a
395 if out is specified, it is assumed to be a file-like object that has a
396 write() method. stdout and stderr will be redirected to out.'''
396 write() method. stdout and stderr will be redirected to out.'''
397 try:
397 try:
398 sys.stdout.flush()
398 sys.stdout.flush()
399 except Exception:
399 except Exception:
400 pass
400 pass
401 def py2shell(val):
401 def py2shell(val):
402 'convert python object into string that is useful to shell'
402 'convert python object into string that is useful to shell'
403 if val is None or val is False:
403 if val is None or val is False:
404 return '0'
404 return '0'
405 if val is True:
405 if val is True:
406 return '1'
406 return '1'
407 return str(val)
407 return str(val)
408 origcmd = cmd
408 origcmd = cmd
409 cmd = quotecommand(cmd)
409 cmd = quotecommand(cmd)
410 env = dict(os.environ)
410 env = dict(os.environ)
411 env.update((k, py2shell(v)) for k, v in environ.iteritems())
411 env.update((k, py2shell(v)) for k, v in environ.iteritems())
412 env['HG'] = hgexecutable()
412 env['HG'] = hgexecutable()
413 if out is None or out == sys.__stdout__:
413 if out is None or out == sys.__stdout__:
414 rc = subprocess.call(cmd, shell=True, close_fds=closefds,
414 rc = subprocess.call(cmd, shell=True, close_fds=closefds,
415 env=env, cwd=cwd)
415 env=env, cwd=cwd)
416 else:
416 else:
417 proc = subprocess.Popen(cmd, shell=True, close_fds=closefds,
417 proc = subprocess.Popen(cmd, shell=True, close_fds=closefds,
418 env=env, cwd=cwd, stdout=subprocess.PIPE,
418 env=env, cwd=cwd, stdout=subprocess.PIPE,
419 stderr=subprocess.STDOUT)
419 stderr=subprocess.STDOUT)
420 for line in proc.stdout:
420 for line in proc.stdout:
421 out.write(line)
421 out.write(line)
422 proc.wait()
422 proc.wait()
423 rc = proc.returncode
423 rc = proc.returncode
424 if sys.platform == 'OpenVMS' and rc & 1:
424 if sys.platform == 'OpenVMS' and rc & 1:
425 rc = 0
425 rc = 0
426 if rc and onerr:
426 if rc and onerr:
427 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
427 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
428 explainexit(rc)[0])
428 explainexit(rc)[0])
429 if errprefix:
429 if errprefix:
430 errmsg = '%s: %s' % (errprefix, errmsg)
430 errmsg = '%s: %s' % (errprefix, errmsg)
431 try:
431 try:
432 onerr.warn(errmsg + '\n')
432 onerr.warn(errmsg + '\n')
433 except AttributeError:
433 except AttributeError:
434 raise onerr(errmsg)
434 raise onerr(errmsg)
435 return rc
435 return rc
436
436
437 def checksignature(func):
437 def checksignature(func):
438 '''wrap a function with code to check for calling errors'''
438 '''wrap a function with code to check for calling errors'''
439 def check(*args, **kwargs):
439 def check(*args, **kwargs):
440 try:
440 try:
441 return func(*args, **kwargs)
441 return func(*args, **kwargs)
442 except TypeError:
442 except TypeError:
443 if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
443 if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
444 raise error.SignatureError
444 raise error.SignatureError
445 raise
445 raise
446
446
447 return check
447 return check
448
448
449 def copyfile(src, dest):
449 def copyfile(src, dest):
450 "copy a file, preserving mode and atime/mtime"
450 "copy a file, preserving mode and atime/mtime"
451 if os.path.islink(src):
451 if os.path.islink(src):
452 try:
452 try:
453 os.unlink(dest)
453 os.unlink(dest)
454 except OSError:
454 except OSError:
455 pass
455 pass
456 os.symlink(os.readlink(src), dest)
456 os.symlink(os.readlink(src), dest)
457 else:
457 else:
458 try:
458 try:
459 shutil.copyfile(src, dest)
459 shutil.copyfile(src, dest)
460 shutil.copymode(src, dest)
460 shutil.copymode(src, dest)
461 except shutil.Error, inst:
461 except shutil.Error, inst:
462 raise Abort(str(inst))
462 raise Abort(str(inst))
463
463
464 def copyfiles(src, dst, hardlink=None):
464 def copyfiles(src, dst, hardlink=None):
465 """Copy a directory tree using hardlinks if possible"""
465 """Copy a directory tree using hardlinks if possible"""
466
466
467 if hardlink is None:
467 if hardlink is None:
468 hardlink = (os.stat(src).st_dev ==
468 hardlink = (os.stat(src).st_dev ==
469 os.stat(os.path.dirname(dst)).st_dev)
469 os.stat(os.path.dirname(dst)).st_dev)
470
470
471 num = 0
471 num = 0
472 if os.path.isdir(src):
472 if os.path.isdir(src):
473 os.mkdir(dst)
473 os.mkdir(dst)
474 for name, kind in osutil.listdir(src):
474 for name, kind in osutil.listdir(src):
475 srcname = os.path.join(src, name)
475 srcname = os.path.join(src, name)
476 dstname = os.path.join(dst, name)
476 dstname = os.path.join(dst, name)
477 hardlink, n = copyfiles(srcname, dstname, hardlink)
477 hardlink, n = copyfiles(srcname, dstname, hardlink)
478 num += n
478 num += n
479 else:
479 else:
480 if hardlink:
480 if hardlink:
481 try:
481 try:
482 oslink(src, dst)
482 oslink(src, dst)
483 except (IOError, OSError):
483 except (IOError, OSError):
484 hardlink = False
484 hardlink = False
485 shutil.copy(src, dst)
485 shutil.copy(src, dst)
486 else:
486 else:
487 shutil.copy(src, dst)
487 shutil.copy(src, dst)
488 num += 1
488 num += 1
489
489
490 return hardlink, num
490 return hardlink, num
491
491
492 _winreservednames = '''con prn aux nul
492 _winreservednames = '''con prn aux nul
493 com1 com2 com3 com4 com5 com6 com7 com8 com9
493 com1 com2 com3 com4 com5 com6 com7 com8 com9
494 lpt1 lpt2 lpt3 lpt4 lpt5 lpt6 lpt7 lpt8 lpt9'''.split()
494 lpt1 lpt2 lpt3 lpt4 lpt5 lpt6 lpt7 lpt8 lpt9'''.split()
495 _winreservedchars = ':*?"<>|'
495 _winreservedchars = ':*?"<>|'
496 def checkwinfilename(path):
496 def checkwinfilename(path):
497 '''Check that the base-relative path is a valid filename on Windows.
497 '''Check that the base-relative path is a valid filename on Windows.
498 Returns None if the path is ok, or a UI string describing the problem.
498 Returns None if the path is ok, or a UI string describing the problem.
499
499
500 >>> checkwinfilename("just/a/normal/path")
500 >>> checkwinfilename("just/a/normal/path")
501 >>> checkwinfilename("foo/bar/con.xml")
501 >>> checkwinfilename("foo/bar/con.xml")
502 "filename contains 'con', which is reserved on Windows"
502 "filename contains 'con', which is reserved on Windows"
503 >>> checkwinfilename("foo/con.xml/bar")
503 >>> checkwinfilename("foo/con.xml/bar")
504 "filename contains 'con', which is reserved on Windows"
504 "filename contains 'con', which is reserved on Windows"
505 >>> checkwinfilename("foo/bar/xml.con")
505 >>> checkwinfilename("foo/bar/xml.con")
506 >>> checkwinfilename("foo/bar/AUX/bla.txt")
506 >>> checkwinfilename("foo/bar/AUX/bla.txt")
507 "filename contains 'AUX', which is reserved on Windows"
507 "filename contains 'AUX', which is reserved on Windows"
508 >>> checkwinfilename("foo/bar/bla:.txt")
508 >>> checkwinfilename("foo/bar/bla:.txt")
509 "filename contains ':', which is reserved on Windows"
509 "filename contains ':', which is reserved on Windows"
510 >>> checkwinfilename("foo/bar/b\07la.txt")
510 >>> checkwinfilename("foo/bar/b\07la.txt")
511 "filename contains '\\\\x07', which is invalid on Windows"
511 "filename contains '\\\\x07', which is invalid on Windows"
512 >>> checkwinfilename("foo/bar/bla ")
512 >>> checkwinfilename("foo/bar/bla ")
513 "filename ends with ' ', which is not allowed on Windows"
513 "filename ends with ' ', which is not allowed on Windows"
514 '''
514 '''
515 for n in path.replace('\\', '/').split('/'):
515 for n in path.replace('\\', '/').split('/'):
516 if not n:
516 if not n:
517 continue
517 continue
518 for c in n:
518 for c in n:
519 if c in _winreservedchars:
519 if c in _winreservedchars:
520 return _("filename contains '%s', which is reserved "
520 return _("filename contains '%s', which is reserved "
521 "on Windows") % c
521 "on Windows") % c
522 if ord(c) <= 31:
522 if ord(c) <= 31:
523 return _("filename contains %r, which is invalid "
523 return _("filename contains %r, which is invalid "
524 "on Windows") % c
524 "on Windows") % c
525 base = n.split('.')[0]
525 base = n.split('.')[0]
526 if base and base.lower() in _winreservednames:
526 if base and base.lower() in _winreservednames:
527 return _("filename contains '%s', which is reserved "
527 return _("filename contains '%s', which is reserved "
528 "on Windows") % base
528 "on Windows") % base
529 t = n[-1]
529 t = n[-1]
530 if t in '. ':
530 if t in '. ':
531 return _("filename ends with '%s', which is not allowed "
531 return _("filename ends with '%s', which is not allowed "
532 "on Windows") % t
532 "on Windows") % t
533
533
534 if os.name == 'nt':
534 if os.name == 'nt':
535 checkosfilename = checkwinfilename
535 checkosfilename = checkwinfilename
536 else:
536 else:
537 checkosfilename = platform.checkosfilename
537 checkosfilename = platform.checkosfilename
538
538
539 def makelock(info, pathname):
539 def makelock(info, pathname):
540 try:
540 try:
541 return os.symlink(info, pathname)
541 return os.symlink(info, pathname)
542 except OSError, why:
542 except OSError, why:
543 if why.errno == errno.EEXIST:
543 if why.errno == errno.EEXIST:
544 raise
544 raise
545 except AttributeError: # no symlink in os
545 except AttributeError: # no symlink in os
546 pass
546 pass
547
547
548 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
548 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
549 os.write(ld, info)
549 os.write(ld, info)
550 os.close(ld)
550 os.close(ld)
551
551
552 def readlock(pathname):
552 def readlock(pathname):
553 try:
553 try:
554 return os.readlink(pathname)
554 return os.readlink(pathname)
555 except OSError, why:
555 except OSError, why:
556 if why.errno not in (errno.EINVAL, errno.ENOSYS):
556 if why.errno not in (errno.EINVAL, errno.ENOSYS):
557 raise
557 raise
558 except AttributeError: # no symlink in os
558 except AttributeError: # no symlink in os
559 pass
559 pass
560 fp = posixfile(pathname)
560 fp = posixfile(pathname)
561 r = fp.read()
561 r = fp.read()
562 fp.close()
562 fp.close()
563 return r
563 return r
564
564
565 def fstat(fp):
565 def fstat(fp):
566 '''stat file object that may not have fileno method.'''
566 '''stat file object that may not have fileno method.'''
567 try:
567 try:
568 return os.fstat(fp.fileno())
568 return os.fstat(fp.fileno())
569 except AttributeError:
569 except AttributeError:
570 return os.stat(fp.name)
570 return os.stat(fp.name)
571
571
572 # File system features
572 # File system features
573
573
574 def checkcase(path):
574 def checkcase(path):
575 """
575 """
576 Check whether the given path is on a case-sensitive filesystem
576 Check whether the given path is on a case-sensitive filesystem
577
577
578 Requires a path (like /foo/.hg) ending with a foldable final
578 Requires a path (like /foo/.hg) ending with a foldable final
579 directory component.
579 directory component.
580 """
580 """
581 s1 = os.stat(path)
581 s1 = os.stat(path)
582 d, b = os.path.split(path)
582 d, b = os.path.split(path)
583 p2 = os.path.join(d, b.upper())
583 p2 = os.path.join(d, b.upper())
584 if path == p2:
584 if path == p2:
585 p2 = os.path.join(d, b.lower())
585 p2 = os.path.join(d, b.lower())
586 try:
586 try:
587 s2 = os.stat(p2)
587 s2 = os.stat(p2)
588 if s2 == s1:
588 if s2 == s1:
589 return False
589 return False
590 return True
590 return True
591 except OSError:
591 except OSError:
592 return True
592 return True
593
593
594 _fspathcache = {}
594 _fspathcache = {}
595 def fspath(name, root):
595 def fspath(name, root):
596 '''Get name in the case stored in the filesystem
596 '''Get name in the case stored in the filesystem
597
597
598 The name is either relative to root, or it is an absolute path starting
598 The name is either relative to root, or it is an absolute path starting
599 with root. Note that this function is unnecessary, and should not be
599 with root. Note that this function is unnecessary, and should not be
600 called, for case-sensitive filesystems (simply because it's expensive).
600 called, for case-sensitive filesystems (simply because it's expensive).
601 '''
601 '''
602 # If name is absolute, make it relative
602 # If name is absolute, make it relative
603 if name.lower().startswith(root.lower()):
603 if name.lower().startswith(root.lower()):
604 l = len(root)
604 l = len(root)
605 if name[l] == os.sep or name[l] == os.altsep:
605 if name[l] == os.sep or name[l] == os.altsep:
606 l = l + 1
606 l = l + 1
607 name = name[l:]
607 name = name[l:]
608
608
609 if not os.path.lexists(os.path.join(root, name)):
609 if not os.path.lexists(os.path.join(root, name)):
610 return None
610 return None
611
611
612 seps = os.sep
612 seps = os.sep
613 if os.altsep:
613 if os.altsep:
614 seps = seps + os.altsep
614 seps = seps + os.altsep
615 # Protect backslashes. This gets silly very quickly.
615 # Protect backslashes. This gets silly very quickly.
616 seps.replace('\\','\\\\')
616 seps.replace('\\','\\\\')
617 pattern = re.compile(r'([^%s]+)|([%s]+)' % (seps, seps))
617 pattern = re.compile(r'([^%s]+)|([%s]+)' % (seps, seps))
618 dir = os.path.normcase(os.path.normpath(root))
618 dir = os.path.normcase(os.path.normpath(root))
619 result = []
619 result = []
620 for part, sep in pattern.findall(name):
620 for part, sep in pattern.findall(name):
621 if sep:
621 if sep:
622 result.append(sep)
622 result.append(sep)
623 continue
623 continue
624
624
625 if dir not in _fspathcache:
625 if dir not in _fspathcache:
626 _fspathcache[dir] = os.listdir(dir)
626 _fspathcache[dir] = os.listdir(dir)
627 contents = _fspathcache[dir]
627 contents = _fspathcache[dir]
628
628
629 lpart = part.lower()
629 lpart = part.lower()
630 lenp = len(part)
630 lenp = len(part)
631 for n in contents:
631 for n in contents:
632 if lenp == len(n) and n.lower() == lpart:
632 if lenp == len(n) and n.lower() == lpart:
633 result.append(n)
633 result.append(n)
634 break
634 break
635 else:
635 else:
636 # Cannot happen, as the file exists!
636 # Cannot happen, as the file exists!
637 result.append(part)
637 result.append(part)
638 dir = os.path.join(dir, lpart)
638 dir = os.path.join(dir, lpart)
639
639
640 return ''.join(result)
640 return ''.join(result)
641
641
642 def checknlink(testfile):
642 def checknlink(testfile):
643 '''check whether hardlink count reporting works properly'''
643 '''check whether hardlink count reporting works properly'''
644
644
645 # testfile may be open, so we need a separate file for checking to
645 # testfile may be open, so we need a separate file for checking to
646 # work around issue2543 (or testfile may get lost on Samba shares)
646 # work around issue2543 (or testfile may get lost on Samba shares)
647 f1 = testfile + ".hgtmp1"
647 f1 = testfile + ".hgtmp1"
648 if os.path.lexists(f1):
648 if os.path.lexists(f1):
649 return False
649 return False
650 try:
650 try:
651 posixfile(f1, 'w').close()
651 posixfile(f1, 'w').close()
652 except IOError:
652 except IOError:
653 return False
653 return False
654
654
655 f2 = testfile + ".hgtmp2"
655 f2 = testfile + ".hgtmp2"
656 fd = None
656 fd = None
657 try:
657 try:
658 try:
658 try:
659 oslink(f1, f2)
659 oslink(f1, f2)
660 except OSError:
660 except OSError:
661 return False
661 return False
662
662
663 # nlinks() may behave differently for files on Windows shares if
663 # nlinks() may behave differently for files on Windows shares if
664 # the file is open.
664 # the file is open.
665 fd = posixfile(f2)
665 fd = posixfile(f2)
666 return nlinks(f2) > 1
666 return nlinks(f2) > 1
667 finally:
667 finally:
668 if fd is not None:
668 if fd is not None:
669 fd.close()
669 fd.close()
670 for f in (f1, f2):
670 for f in (f1, f2):
671 try:
671 try:
672 os.unlink(f)
672 os.unlink(f)
673 except OSError:
673 except OSError:
674 pass
674 pass
675
675
676 return False
676 return False
677
677
678 def endswithsep(path):
678 def endswithsep(path):
679 '''Check path ends with os.sep or os.altsep.'''
679 '''Check path ends with os.sep or os.altsep.'''
680 return path.endswith(os.sep) or os.altsep and path.endswith(os.altsep)
680 return path.endswith(os.sep) or os.altsep and path.endswith(os.altsep)
681
681
682 def splitpath(path):
682 def splitpath(path):
683 '''Split path by os.sep.
683 '''Split path by os.sep.
684 Note that this function does not use os.altsep because this is
684 Note that this function does not use os.altsep because this is
685 an alternative of simple "xxx.split(os.sep)".
685 an alternative of simple "xxx.split(os.sep)".
686 It is recommended to use os.path.normpath() before using this
686 It is recommended to use os.path.normpath() before using this
687 function if need.'''
687 function if need.'''
688 return path.split(os.sep)
688 return path.split(os.sep)
689
689
690 def gui():
690 def gui():
691 '''Are we running in a GUI?'''
691 '''Are we running in a GUI?'''
692 if sys.platform == 'darwin':
692 if sys.platform == 'darwin':
693 if 'SSH_CONNECTION' in os.environ:
693 if 'SSH_CONNECTION' in os.environ:
694 # handle SSH access to a box where the user is logged in
694 # handle SSH access to a box where the user is logged in
695 return False
695 return False
696 elif getattr(osutil, 'isgui', None):
696 elif getattr(osutil, 'isgui', None):
697 # check if a CoreGraphics session is available
697 # check if a CoreGraphics session is available
698 return osutil.isgui()
698 return osutil.isgui()
699 else:
699 else:
700 # pure build; use a safe default
700 # pure build; use a safe default
701 return True
701 return True
702 else:
702 else:
703 return os.name == "nt" or os.environ.get("DISPLAY")
703 return os.name == "nt" or os.environ.get("DISPLAY")
704
704
705 def mktempcopy(name, emptyok=False, createmode=None):
705 def mktempcopy(name, emptyok=False, createmode=None):
706 """Create a temporary file with the same contents from name
706 """Create a temporary file with the same contents from name
707
707
708 The permission bits are copied from the original file.
708 The permission bits are copied from the original file.
709
709
710 If the temporary file is going to be truncated immediately, you
710 If the temporary file is going to be truncated immediately, you
711 can use emptyok=True as an optimization.
711 can use emptyok=True as an optimization.
712
712
713 Returns the name of the temporary file.
713 Returns the name of the temporary file.
714 """
714 """
715 d, fn = os.path.split(name)
715 d, fn = os.path.split(name)
716 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
716 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
717 os.close(fd)
717 os.close(fd)
718 # Temporary files are created with mode 0600, which is usually not
718 # Temporary files are created with mode 0600, which is usually not
719 # what we want. If the original file already exists, just copy
719 # what we want. If the original file already exists, just copy
720 # its mode. Otherwise, manually obey umask.
720 # its mode. Otherwise, manually obey umask.
721 copymode(name, temp, createmode)
721 copymode(name, temp, createmode)
722 if emptyok:
722 if emptyok:
723 return temp
723 return temp
724 try:
724 try:
725 try:
725 try:
726 ifp = posixfile(name, "rb")
726 ifp = posixfile(name, "rb")
727 except IOError, inst:
727 except IOError, inst:
728 if inst.errno == errno.ENOENT:
728 if inst.errno == errno.ENOENT:
729 return temp
729 return temp
730 if not getattr(inst, 'filename', None):
730 if not getattr(inst, 'filename', None):
731 inst.filename = name
731 inst.filename = name
732 raise
732 raise
733 ofp = posixfile(temp, "wb")
733 ofp = posixfile(temp, "wb")
734 for chunk in filechunkiter(ifp):
734 for chunk in filechunkiter(ifp):
735 ofp.write(chunk)
735 ofp.write(chunk)
736 ifp.close()
736 ifp.close()
737 ofp.close()
737 ofp.close()
738 except:
738 except:
739 try: os.unlink(temp)
739 try: os.unlink(temp)
740 except: pass
740 except: pass
741 raise
741 raise
742 return temp
742 return temp
743
743
744 class atomictempfile(object):
744 class atomictempfile(object):
745 '''writeable file object that atomically updates a file
745 '''writeable file object that atomically updates a file
746
746
747 All writes will go to a temporary copy of the original file. Call
747 All writes will go to a temporary copy of the original file. Call
748 rename() when you are done writing, and atomictempfile will rename
748 rename() when you are done writing, and atomictempfile will rename
749 the temporary copy to the original name, making the changes visible.
749 the temporary copy to the original name, making the changes visible.
750
750
751 Unlike other file-like objects, close() discards your writes by
751 Unlike other file-like objects, close() discards your writes by
752 simply deleting the temporary file.
752 simply deleting the temporary file.
753 '''
753 '''
754 def __init__(self, name, mode='w+b', createmode=None):
754 def __init__(self, name, mode='w+b', createmode=None):
755 self.__name = name # permanent name
755 self.__name = name # permanent name
756 self._tempname = mktempcopy(name, emptyok=('w' in mode),
756 self._tempname = mktempcopy(name, emptyok=('w' in mode),
757 createmode=createmode)
757 createmode=createmode)
758 self._fp = posixfile(self._tempname, mode)
758 self._fp = posixfile(self._tempname, mode)
759
759
760 # delegated methods
760 # delegated methods
761 self.write = self._fp.write
761 self.write = self._fp.write
762 self.fileno = self._fp.fileno
762 self.fileno = self._fp.fileno
763
763
764 def rename(self):
764 def rename(self):
765 if not self._fp.closed:
765 if not self._fp.closed:
766 self._fp.close()
766 self._fp.close()
767 rename(self._tempname, localpath(self.__name))
767 rename(self._tempname, localpath(self.__name))
768
768
769 def close(self):
769 def close(self):
770 if not self._fp.closed:
770 if not self._fp.closed:
771 try:
771 try:
772 os.unlink(self._tempname)
772 os.unlink(self._tempname)
773 except OSError:
773 except OSError:
774 pass
774 pass
775 self._fp.close()
775 self._fp.close()
776
776
777 def __del__(self):
777 def __del__(self):
778 if safehasattr(self, '_fp'): # constructor actually did something
778 if safehasattr(self, '_fp'): # constructor actually did something
779 self.close()
779 self.close()
780
780
781 def makedirs(name, mode=None):
781 def makedirs(name, mode=None):
782 """recursive directory creation with parent mode inheritance"""
782 """recursive directory creation with parent mode inheritance"""
783 parent = os.path.abspath(os.path.dirname(name))
783 parent = os.path.abspath(os.path.dirname(name))
784 try:
784 try:
785 os.mkdir(name)
785 os.mkdir(name)
786 if mode is not None:
786 if mode is not None:
787 os.chmod(name, mode)
787 os.chmod(name, mode)
788 return
788 return
789 except OSError, err:
789 except OSError, err:
790 if err.errno == errno.EEXIST:
790 if err.errno == errno.EEXIST:
791 return
791 return
792 if not name or parent == name or err.errno != errno.ENOENT:
792 if not name or parent == name or err.errno != errno.ENOENT:
793 raise
793 raise
794 makedirs(parent, mode)
794 makedirs(parent, mode)
795 makedirs(name, mode)
795 makedirs(name, mode)
796
796
797 def readfile(path):
797 def readfile(path):
798 fp = open(path, 'rb')
798 fp = open(path, 'rb')
799 try:
799 try:
800 return fp.read()
800 return fp.read()
801 finally:
801 finally:
802 fp.close()
802 fp.close()
803
803
804 def writefile(path, text):
804 def writefile(path, text):
805 fp = open(path, 'wb')
805 fp = open(path, 'wb')
806 try:
806 try:
807 fp.write(text)
807 fp.write(text)
808 finally:
808 finally:
809 fp.close()
809 fp.close()
810
810
811 def appendfile(path, text):
811 def appendfile(path, text):
812 fp = open(path, 'ab')
812 fp = open(path, 'ab')
813 try:
813 try:
814 fp.write(text)
814 fp.write(text)
815 finally:
815 finally:
816 fp.close()
816 fp.close()
817
817
818 class chunkbuffer(object):
818 class chunkbuffer(object):
819 """Allow arbitrary sized chunks of data to be efficiently read from an
819 """Allow arbitrary sized chunks of data to be efficiently read from an
820 iterator over chunks of arbitrary size."""
820 iterator over chunks of arbitrary size."""
821
821
822 def __init__(self, in_iter):
822 def __init__(self, in_iter):
823 """in_iter is the iterator that's iterating over the input chunks.
823 """in_iter is the iterator that's iterating over the input chunks.
824 targetsize is how big a buffer to try to maintain."""
824 targetsize is how big a buffer to try to maintain."""
825 def splitbig(chunks):
825 def splitbig(chunks):
826 for chunk in chunks:
826 for chunk in chunks:
827 if len(chunk) > 2**20:
827 if len(chunk) > 2**20:
828 pos = 0
828 pos = 0
829 while pos < len(chunk):
829 while pos < len(chunk):
830 end = pos + 2 ** 18
830 end = pos + 2 ** 18
831 yield chunk[pos:end]
831 yield chunk[pos:end]
832 pos = end
832 pos = end
833 else:
833 else:
834 yield chunk
834 yield chunk
835 self.iter = splitbig(in_iter)
835 self.iter = splitbig(in_iter)
836 self._queue = []
836 self._queue = []
837
837
838 def read(self, l):
838 def read(self, l):
839 """Read L bytes of data from the iterator of chunks of data.
839 """Read L bytes of data from the iterator of chunks of data.
840 Returns less than L bytes if the iterator runs dry."""
840 Returns less than L bytes if the iterator runs dry."""
841 left = l
841 left = l
842 buf = ''
842 buf = ''
843 queue = self._queue
843 queue = self._queue
844 while left > 0:
844 while left > 0:
845 # refill the queue
845 # refill the queue
846 if not queue:
846 if not queue:
847 target = 2**18
847 target = 2**18
848 for chunk in self.iter:
848 for chunk in self.iter:
849 queue.append(chunk)
849 queue.append(chunk)
850 target -= len(chunk)
850 target -= len(chunk)
851 if target <= 0:
851 if target <= 0:
852 break
852 break
853 if not queue:
853 if not queue:
854 break
854 break
855
855
856 chunk = queue.pop(0)
856 chunk = queue.pop(0)
857 left -= len(chunk)
857 left -= len(chunk)
858 if left < 0:
858 if left < 0:
859 queue.insert(0, chunk[left:])
859 queue.insert(0, chunk[left:])
860 buf += chunk[:left]
860 buf += chunk[:left]
861 else:
861 else:
862 buf += chunk
862 buf += chunk
863
863
864 return buf
864 return buf
865
865
866 def filechunkiter(f, size=65536, limit=None):
866 def filechunkiter(f, size=65536, limit=None):
867 """Create a generator that produces the data in the file size
867 """Create a generator that produces the data in the file size
868 (default 65536) bytes at a time, up to optional limit (default is
868 (default 65536) bytes at a time, up to optional limit (default is
869 to read all data). Chunks may be less than size bytes if the
869 to read all data). Chunks may be less than size bytes if the
870 chunk is the last chunk in the file, or the file is a socket or
870 chunk is the last chunk in the file, or the file is a socket or
871 some other type of file that sometimes reads less data than is
871 some other type of file that sometimes reads less data than is
872 requested."""
872 requested."""
873 assert size >= 0
873 assert size >= 0
874 assert limit is None or limit >= 0
874 assert limit is None or limit >= 0
875 while True:
875 while True:
876 if limit is None:
876 if limit is None:
877 nbytes = size
877 nbytes = size
878 else:
878 else:
879 nbytes = min(limit, size)
879 nbytes = min(limit, size)
880 s = nbytes and f.read(nbytes)
880 s = nbytes and f.read(nbytes)
881 if not s:
881 if not s:
882 break
882 break
883 if limit:
883 if limit:
884 limit -= len(s)
884 limit -= len(s)
885 yield s
885 yield s
886
886
887 def makedate():
887 def makedate():
888 lt = time.localtime()
888 lt = time.localtime()
889 if lt[8] == 1 and time.daylight:
889 if lt[8] == 1 and time.daylight:
890 tz = time.altzone
890 tz = time.altzone
891 else:
891 else:
892 tz = time.timezone
892 tz = time.timezone
893 t = time.mktime(lt)
893 t = time.mktime(lt)
894 if t < 0:
894 if t < 0:
895 hint = _("check your clock")
895 hint = _("check your clock")
896 raise Abort(_("negative timestamp: %d") % t, hint=hint)
896 raise Abort(_("negative timestamp: %d") % t, hint=hint)
897 return t, tz
897 return t, tz
898
898
899 def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
899 def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
900 """represent a (unixtime, offset) tuple as a localized time.
900 """represent a (unixtime, offset) tuple as a localized time.
901 unixtime is seconds since the epoch, and offset is the time zone's
901 unixtime is seconds since the epoch, and offset is the time zone's
902 number of seconds away from UTC. if timezone is false, do not
902 number of seconds away from UTC. if timezone is false, do not
903 append time zone to string."""
903 append time zone to string."""
904 t, tz = date or makedate()
904 t, tz = date or makedate()
905 if t < 0:
905 if t < 0:
906 t = 0 # time.gmtime(lt) fails on Windows for lt < -43200
906 t = 0 # time.gmtime(lt) fails on Windows for lt < -43200
907 tz = 0
907 tz = 0
908 if "%1" in format or "%2" in format:
908 if "%1" in format or "%2" in format:
909 sign = (tz > 0) and "-" or "+"
909 sign = (tz > 0) and "-" or "+"
910 minutes = abs(tz) // 60
910 minutes = abs(tz) // 60
911 format = format.replace("%1", "%c%02d" % (sign, minutes // 60))
911 format = format.replace("%1", "%c%02d" % (sign, minutes // 60))
912 format = format.replace("%2", "%02d" % (minutes % 60))
912 format = format.replace("%2", "%02d" % (minutes % 60))
913 s = time.strftime(format, time.gmtime(float(t) - tz))
913 s = time.strftime(format, time.gmtime(float(t) - tz))
914 return s
914 return s
915
915
916 def shortdate(date=None):
916 def shortdate(date=None):
917 """turn (timestamp, tzoff) tuple into iso 8631 date."""
917 """turn (timestamp, tzoff) tuple into iso 8631 date."""
918 return datestr(date, format='%Y-%m-%d')
918 return datestr(date, format='%Y-%m-%d')
919
919
920 def strdate(string, format, defaults=[]):
920 def strdate(string, format, defaults=[]):
921 """parse a localized time string and return a (unixtime, offset) tuple.
921 """parse a localized time string and return a (unixtime, offset) tuple.
922 if the string cannot be parsed, ValueError is raised."""
922 if the string cannot be parsed, ValueError is raised."""
923 def timezone(string):
923 def timezone(string):
924 tz = string.split()[-1]
924 tz = string.split()[-1]
925 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
925 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
926 sign = (tz[0] == "+") and 1 or -1
926 sign = (tz[0] == "+") and 1 or -1
927 hours = int(tz[1:3])
927 hours = int(tz[1:3])
928 minutes = int(tz[3:5])
928 minutes = int(tz[3:5])
929 return -sign * (hours * 60 + minutes) * 60
929 return -sign * (hours * 60 + minutes) * 60
930 if tz == "GMT" or tz == "UTC":
930 if tz == "GMT" or tz == "UTC":
931 return 0
931 return 0
932 return None
932 return None
933
933
934 # NOTE: unixtime = localunixtime + offset
934 # NOTE: unixtime = localunixtime + offset
935 offset, date = timezone(string), string
935 offset, date = timezone(string), string
936 if offset is not None:
936 if offset is not None:
937 date = " ".join(string.split()[:-1])
937 date = " ".join(string.split()[:-1])
938
938
939 # add missing elements from defaults
939 # add missing elements from defaults
940 usenow = False # default to using biased defaults
940 usenow = False # default to using biased defaults
941 for part in ("S", "M", "HI", "d", "mb", "yY"): # decreasing specificity
941 for part in ("S", "M", "HI", "d", "mb", "yY"): # decreasing specificity
942 found = [True for p in part if ("%"+p) in format]
942 found = [True for p in part if ("%"+p) in format]
943 if not found:
943 if not found:
944 date += "@" + defaults[part][usenow]
944 date += "@" + defaults[part][usenow]
945 format += "@%" + part[0]
945 format += "@%" + part[0]
946 else:
946 else:
947 # We've found a specific time element, less specific time
947 # We've found a specific time element, less specific time
948 # elements are relative to today
948 # elements are relative to today
949 usenow = True
949 usenow = True
950
950
951 timetuple = time.strptime(date, format)
951 timetuple = time.strptime(date, format)
952 localunixtime = int(calendar.timegm(timetuple))
952 localunixtime = int(calendar.timegm(timetuple))
953 if offset is None:
953 if offset is None:
954 # local timezone
954 # local timezone
955 unixtime = int(time.mktime(timetuple))
955 unixtime = int(time.mktime(timetuple))
956 offset = unixtime - localunixtime
956 offset = unixtime - localunixtime
957 else:
957 else:
958 unixtime = localunixtime + offset
958 unixtime = localunixtime + offset
959 return unixtime, offset
959 return unixtime, offset
960
960
961 def parsedate(date, formats=None, bias={}):
961 def parsedate(date, formats=None, bias={}):
962 """parse a localized date/time and return a (unixtime, offset) tuple.
962 """parse a localized date/time and return a (unixtime, offset) tuple.
963
963
964 The date may be a "unixtime offset" string or in one of the specified
964 The date may be a "unixtime offset" string or in one of the specified
965 formats. If the date already is a (unixtime, offset) tuple, it is returned.
965 formats. If the date already is a (unixtime, offset) tuple, it is returned.
966 """
966 """
967 if not date:
967 if not date:
968 return 0, 0
968 return 0, 0
969 if isinstance(date, tuple) and len(date) == 2:
969 if isinstance(date, tuple) and len(date) == 2:
970 return date
970 return date
971 if not formats:
971 if not formats:
972 formats = defaultdateformats
972 formats = defaultdateformats
973 date = date.strip()
973 date = date.strip()
974 try:
974 try:
975 when, offset = map(int, date.split(' '))
975 when, offset = map(int, date.split(' '))
976 except ValueError:
976 except ValueError:
977 # fill out defaults
977 # fill out defaults
978 now = makedate()
978 now = makedate()
979 defaults = {}
979 defaults = {}
980 for part in ("d", "mb", "yY", "HI", "M", "S"):
980 for part in ("d", "mb", "yY", "HI", "M", "S"):
981 # this piece is for rounding the specific end of unknowns
981 # this piece is for rounding the specific end of unknowns
982 b = bias.get(part)
982 b = bias.get(part)
983 if b is None:
983 if b is None:
984 if part[0] in "HMS":
984 if part[0] in "HMS":
985 b = "00"
985 b = "00"
986 else:
986 else:
987 b = "0"
987 b = "0"
988
988
989 # this piece is for matching the generic end to today's date
989 # this piece is for matching the generic end to today's date
990 n = datestr(now, "%" + part[0])
990 n = datestr(now, "%" + part[0])
991
991
992 defaults[part] = (b, n)
992 defaults[part] = (b, n)
993
993
994 for format in formats:
994 for format in formats:
995 try:
995 try:
996 when, offset = strdate(date, format, defaults)
996 when, offset = strdate(date, format, defaults)
997 except (ValueError, OverflowError):
997 except (ValueError, OverflowError):
998 pass
998 pass
999 else:
999 else:
1000 break
1000 break
1001 else:
1001 else:
1002 raise Abort(_('invalid date: %r') % date)
1002 raise Abort(_('invalid date: %r') % date)
1003 # validate explicit (probably user-specified) date and
1003 # validate explicit (probably user-specified) date and
1004 # time zone offset. values must fit in signed 32 bits for
1004 # time zone offset. values must fit in signed 32 bits for
1005 # current 32-bit linux runtimes. timezones go from UTC-12
1005 # current 32-bit linux runtimes. timezones go from UTC-12
1006 # to UTC+14
1006 # to UTC+14
1007 if abs(when) > 0x7fffffff:
1007 if abs(when) > 0x7fffffff:
1008 raise Abort(_('date exceeds 32 bits: %d') % when)
1008 raise Abort(_('date exceeds 32 bits: %d') % when)
1009 if when < 0:
1009 if when < 0:
1010 raise Abort(_('negative date value: %d') % when)
1010 raise Abort(_('negative date value: %d') % when)
1011 if offset < -50400 or offset > 43200:
1011 if offset < -50400 or offset > 43200:
1012 raise Abort(_('impossible time zone offset: %d') % offset)
1012 raise Abort(_('impossible time zone offset: %d') % offset)
1013 return when, offset
1013 return when, offset
1014
1014
1015 def matchdate(date):
1015 def matchdate(date):
1016 """Return a function that matches a given date match specifier
1016 """Return a function that matches a given date match specifier
1017
1017
1018 Formats include:
1018 Formats include:
1019
1019
1020 '{date}' match a given date to the accuracy provided
1020 '{date}' match a given date to the accuracy provided
1021
1021
1022 '<{date}' on or before a given date
1022 '<{date}' on or before a given date
1023
1023
1024 '>{date}' on or after a given date
1024 '>{date}' on or after a given date
1025
1025
1026 >>> p1 = parsedate("10:29:59")
1026 >>> p1 = parsedate("10:29:59")
1027 >>> p2 = parsedate("10:30:00")
1027 >>> p2 = parsedate("10:30:00")
1028 >>> p3 = parsedate("10:30:59")
1028 >>> p3 = parsedate("10:30:59")
1029 >>> p4 = parsedate("10:31:00")
1029 >>> p4 = parsedate("10:31:00")
1030 >>> p5 = parsedate("Sep 15 10:30:00 1999")
1030 >>> p5 = parsedate("Sep 15 10:30:00 1999")
1031 >>> f = matchdate("10:30")
1031 >>> f = matchdate("10:30")
1032 >>> f(p1[0])
1032 >>> f(p1[0])
1033 False
1033 False
1034 >>> f(p2[0])
1034 >>> f(p2[0])
1035 True
1035 True
1036 >>> f(p3[0])
1036 >>> f(p3[0])
1037 True
1037 True
1038 >>> f(p4[0])
1038 >>> f(p4[0])
1039 False
1039 False
1040 >>> f(p5[0])
1040 >>> f(p5[0])
1041 False
1041 False
1042 """
1042 """
1043
1043
1044 def lower(date):
1044 def lower(date):
1045 d = dict(mb="1", d="1")
1045 d = dict(mb="1", d="1")
1046 return parsedate(date, extendeddateformats, d)[0]
1046 return parsedate(date, extendeddateformats, d)[0]
1047
1047
1048 def upper(date):
1048 def upper(date):
1049 d = dict(mb="12", HI="23", M="59", S="59")
1049 d = dict(mb="12", HI="23", M="59", S="59")
1050 for days in ("31", "30", "29"):
1050 for days in ("31", "30", "29"):
1051 try:
1051 try:
1052 d["d"] = days
1052 d["d"] = days
1053 return parsedate(date, extendeddateformats, d)[0]
1053 return parsedate(date, extendeddateformats, d)[0]
1054 except:
1054 except:
1055 pass
1055 pass
1056 d["d"] = "28"
1056 d["d"] = "28"
1057 return parsedate(date, extendeddateformats, d)[0]
1057 return parsedate(date, extendeddateformats, d)[0]
1058
1058
1059 date = date.strip()
1059 date = date.strip()
1060
1060
1061 if not date:
1061 if not date:
1062 raise Abort(_("dates cannot consist entirely of whitespace"))
1062 raise Abort(_("dates cannot consist entirely of whitespace"))
1063 elif date[0] == "<":
1063 elif date[0] == "<":
1064 if not date[1:]:
1064 if not date[1:]:
1065 raise Abort(_("invalid day spec, use '<DATE'"))
1065 raise Abort(_("invalid day spec, use '<DATE'"))
1066 when = upper(date[1:])
1066 when = upper(date[1:])
1067 return lambda x: x <= when
1067 return lambda x: x <= when
1068 elif date[0] == ">":
1068 elif date[0] == ">":
1069 if not date[1:]:
1069 if not date[1:]:
1070 raise Abort(_("invalid day spec, use '>DATE'"))
1070 raise Abort(_("invalid day spec, use '>DATE'"))
1071 when = lower(date[1:])
1071 when = lower(date[1:])
1072 return lambda x: x >= when
1072 return lambda x: x >= when
1073 elif date[0] == "-":
1073 elif date[0] == "-":
1074 try:
1074 try:
1075 days = int(date[1:])
1075 days = int(date[1:])
1076 except ValueError:
1076 except ValueError:
1077 raise Abort(_("invalid day spec: %s") % date[1:])
1077 raise Abort(_("invalid day spec: %s") % date[1:])
1078 if days < 0:
1078 if days < 0:
1079 raise Abort(_("%s must be nonnegative (see 'hg help dates')")
1079 raise Abort(_("%s must be nonnegative (see 'hg help dates')")
1080 % date[1:])
1080 % date[1:])
1081 when = makedate()[0] - days * 3600 * 24
1081 when = makedate()[0] - days * 3600 * 24
1082 return lambda x: x >= when
1082 return lambda x: x >= when
1083 elif " to " in date:
1083 elif " to " in date:
1084 a, b = date.split(" to ")
1084 a, b = date.split(" to ")
1085 start, stop = lower(a), upper(b)
1085 start, stop = lower(a), upper(b)
1086 return lambda x: x >= start and x <= stop
1086 return lambda x: x >= start and x <= stop
1087 else:
1087 else:
1088 start, stop = lower(date), upper(date)
1088 start, stop = lower(date), upper(date)
1089 return lambda x: x >= start and x <= stop
1089 return lambda x: x >= start and x <= stop
1090
1090
1091 def shortuser(user):
1091 def shortuser(user):
1092 """Return a short representation of a user name or email address."""
1092 """Return a short representation of a user name or email address."""
1093 f = user.find('@')
1093 f = user.find('@')
1094 if f >= 0:
1094 if f >= 0:
1095 user = user[:f]
1095 user = user[:f]
1096 f = user.find('<')
1096 f = user.find('<')
1097 if f >= 0:
1097 if f >= 0:
1098 user = user[f + 1:]
1098 user = user[f + 1:]
1099 f = user.find(' ')
1099 f = user.find(' ')
1100 if f >= 0:
1100 if f >= 0:
1101 user = user[:f]
1101 user = user[:f]
1102 f = user.find('.')
1102 f = user.find('.')
1103 if f >= 0:
1103 if f >= 0:
1104 user = user[:f]
1104 user = user[:f]
1105 return user
1105 return user
1106
1106
1107 def email(author):
1107 def email(author):
1108 '''get email of author.'''
1108 '''get email of author.'''
1109 r = author.find('>')
1109 r = author.find('>')
1110 if r == -1:
1110 if r == -1:
1111 r = None
1111 r = None
1112 return author[author.find('<') + 1:r]
1112 return author[author.find('<') + 1:r]
1113
1113
1114 def _ellipsis(text, maxlength):
1114 def _ellipsis(text, maxlength):
1115 if len(text) <= maxlength:
1115 if len(text) <= maxlength:
1116 return text, False
1116 return text, False
1117 else:
1117 else:
1118 return "%s..." % (text[:maxlength - 3]), True
1118 return "%s..." % (text[:maxlength - 3]), True
1119
1119
1120 def ellipsis(text, maxlength=400):
1120 def ellipsis(text, maxlength=400):
1121 """Trim string to at most maxlength (default: 400) characters."""
1121 """Trim string to at most maxlength (default: 400) characters."""
1122 try:
1122 try:
1123 # use unicode not to split at intermediate multi-byte sequence
1123 # use unicode not to split at intermediate multi-byte sequence
1124 utext, truncated = _ellipsis(text.decode(encoding.encoding),
1124 utext, truncated = _ellipsis(text.decode(encoding.encoding),
1125 maxlength)
1125 maxlength)
1126 if not truncated:
1126 if not truncated:
1127 return text
1127 return text
1128 return utext.encode(encoding.encoding)
1128 return utext.encode(encoding.encoding)
1129 except (UnicodeDecodeError, UnicodeEncodeError):
1129 except (UnicodeDecodeError, UnicodeEncodeError):
1130 return _ellipsis(text, maxlength)[0]
1130 return _ellipsis(text, maxlength)[0]
1131
1131
1132 def bytecount(nbytes):
1132 def bytecount(nbytes):
1133 '''return byte count formatted as readable string, with units'''
1133 '''return byte count formatted as readable string, with units'''
1134
1134
1135 units = (
1135 units = (
1136 (100, 1 << 30, _('%.0f GB')),
1136 (100, 1 << 30, _('%.0f GB')),
1137 (10, 1 << 30, _('%.1f GB')),
1137 (10, 1 << 30, _('%.1f GB')),
1138 (1, 1 << 30, _('%.2f GB')),
1138 (1, 1 << 30, _('%.2f GB')),
1139 (100, 1 << 20, _('%.0f MB')),
1139 (100, 1 << 20, _('%.0f MB')),
1140 (10, 1 << 20, _('%.1f MB')),
1140 (10, 1 << 20, _('%.1f MB')),
1141 (1, 1 << 20, _('%.2f MB')),
1141 (1, 1 << 20, _('%.2f MB')),
1142 (100, 1 << 10, _('%.0f KB')),
1142 (100, 1 << 10, _('%.0f KB')),
1143 (10, 1 << 10, _('%.1f KB')),
1143 (10, 1 << 10, _('%.1f KB')),
1144 (1, 1 << 10, _('%.2f KB')),
1144 (1, 1 << 10, _('%.2f KB')),
1145 (1, 1, _('%.0f bytes')),
1145 (1, 1, _('%.0f bytes')),
1146 )
1146 )
1147
1147
1148 for multiplier, divisor, format in units:
1148 for multiplier, divisor, format in units:
1149 if nbytes >= divisor * multiplier:
1149 if nbytes >= divisor * multiplier:
1150 return format % (nbytes / float(divisor))
1150 return format % (nbytes / float(divisor))
1151 return units[-1][2] % nbytes
1151 return units[-1][2] % nbytes
1152
1152
1153 def uirepr(s):
1153 def uirepr(s):
1154 # Avoid double backslash in Windows path repr()
1154 # Avoid double backslash in Windows path repr()
1155 return repr(s).replace('\\\\', '\\')
1155 return repr(s).replace('\\\\', '\\')
1156
1156
1157 # delay import of textwrap
1157 # delay import of textwrap
1158 def MBTextWrapper(**kwargs):
1158 def MBTextWrapper(**kwargs):
1159 class tw(textwrap.TextWrapper):
1159 class tw(textwrap.TextWrapper):
1160 """
1160 """
1161 Extend TextWrapper for double-width characters.
1161 Extend TextWrapper for double-width characters.
1162
1162
1163 Some Asian characters use two terminal columns instead of one.
1163 Some Asian characters use two terminal columns instead of one.
1164 A good example of this behavior can be seen with u'\u65e5\u672c',
1164 A good example of this behavior can be seen with u'\u65e5\u672c',
1165 the two Japanese characters for "Japan":
1165 the two Japanese characters for "Japan":
1166 len() returns 2, but when printed to a terminal, they eat 4 columns.
1166 len() returns 2, but when printed to a terminal, they eat 4 columns.
1167
1167
1168 (Note that this has nothing to do whatsoever with unicode
1168 (Note that this has nothing to do whatsoever with unicode
1169 representation, or encoding of the underlying string)
1169 representation, or encoding of the underlying string)
1170 """
1170 """
1171 def __init__(self, **kwargs):
1171 def __init__(self, **kwargs):
1172 textwrap.TextWrapper.__init__(self, **kwargs)
1172 textwrap.TextWrapper.__init__(self, **kwargs)
1173
1173
1174 def _cutdown(self, str, space_left):
1174 def _cutdown(self, str, space_left):
1175 l = 0
1175 l = 0
1176 ucstr = unicode(str, encoding.encoding)
1176 ucstr = unicode(str, encoding.encoding)
1177 colwidth = unicodedata.east_asian_width
1177 colwidth = unicodedata.east_asian_width
1178 for i in xrange(len(ucstr)):
1178 for i in xrange(len(ucstr)):
1179 l += colwidth(ucstr[i]) in 'WFA' and 2 or 1
1179 l += colwidth(ucstr[i]) in 'WFA' and 2 or 1
1180 if space_left < l:
1180 if space_left < l:
1181 return (ucstr[:i].encode(encoding.encoding),
1181 return (ucstr[:i].encode(encoding.encoding),
1182 ucstr[i:].encode(encoding.encoding))
1182 ucstr[i:].encode(encoding.encoding))
1183 return str, ''
1183 return str, ''
1184
1184
1185 # overriding of base class
1185 # overriding of base class
1186 def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width):
1186 def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width):
1187 space_left = max(width - cur_len, 1)
1187 space_left = max(width - cur_len, 1)
1188
1188
1189 if self.break_long_words:
1189 if self.break_long_words:
1190 cut, res = self._cutdown(reversed_chunks[-1], space_left)
1190 cut, res = self._cutdown(reversed_chunks[-1], space_left)
1191 cur_line.append(cut)
1191 cur_line.append(cut)
1192 reversed_chunks[-1] = res
1192 reversed_chunks[-1] = res
1193 elif not cur_line:
1193 elif not cur_line:
1194 cur_line.append(reversed_chunks.pop())
1194 cur_line.append(reversed_chunks.pop())
1195
1195
1196 global MBTextWrapper
1196 global MBTextWrapper
1197 MBTextWrapper = tw
1197 MBTextWrapper = tw
1198 return tw(**kwargs)
1198 return tw(**kwargs)
1199
1199
1200 def wrap(line, width, initindent='', hangindent=''):
1200 def wrap(line, width, initindent='', hangindent=''):
1201 maxindent = max(len(hangindent), len(initindent))
1201 maxindent = max(len(hangindent), len(initindent))
1202 if width <= maxindent:
1202 if width <= maxindent:
1203 # adjust for weird terminal size
1203 # adjust for weird terminal size
1204 width = max(78, maxindent + 1)
1204 width = max(78, maxindent + 1)
1205 wrapper = MBTextWrapper(width=width,
1205 wrapper = MBTextWrapper(width=width,
1206 initial_indent=initindent,
1206 initial_indent=initindent,
1207 subsequent_indent=hangindent)
1207 subsequent_indent=hangindent)
1208 return wrapper.fill(line)
1208 return wrapper.fill(line)
1209
1209
1210 def iterlines(iterator):
1210 def iterlines(iterator):
1211 for chunk in iterator:
1211 for chunk in iterator:
1212 for line in chunk.splitlines():
1212 for line in chunk.splitlines():
1213 yield line
1213 yield line
1214
1214
1215 def expandpath(path):
1215 def expandpath(path):
1216 return os.path.expanduser(os.path.expandvars(path))
1216 return os.path.expanduser(os.path.expandvars(path))
1217
1217
1218 def hgcmd():
1218 def hgcmd():
1219 """Return the command used to execute current hg
1219 """Return the command used to execute current hg
1220
1220
1221 This is different from hgexecutable() because on Windows we want
1221 This is different from hgexecutable() because on Windows we want
1222 to avoid things opening new shell windows like batch files, so we
1222 to avoid things opening new shell windows like batch files, so we
1223 get either the python call or current executable.
1223 get either the python call or current executable.
1224 """
1224 """
1225 if mainfrozen():
1225 if mainfrozen():
1226 return [sys.executable]
1226 return [sys.executable]
1227 return gethgcmd()
1227 return gethgcmd()
1228
1228
1229 def rundetached(args, condfn):
1229 def rundetached(args, condfn):
1230 """Execute the argument list in a detached process.
1230 """Execute the argument list in a detached process.
1231
1231
1232 condfn is a callable which is called repeatedly and should return
1232 condfn is a callable which is called repeatedly and should return
1233 True once the child process is known to have started successfully.
1233 True once the child process is known to have started successfully.
1234 At this point, the child process PID is returned. If the child
1234 At this point, the child process PID is returned. If the child
1235 process fails to start or finishes before condfn() evaluates to
1235 process fails to start or finishes before condfn() evaluates to
1236 True, return -1.
1236 True, return -1.
1237 """
1237 """
1238 # Windows case is easier because the child process is either
1238 # Windows case is easier because the child process is either
1239 # successfully starting and validating the condition or exiting
1239 # successfully starting and validating the condition or exiting
1240 # on failure. We just poll on its PID. On Unix, if the child
1240 # on failure. We just poll on its PID. On Unix, if the child
1241 # process fails to start, it will be left in a zombie state until
1241 # process fails to start, it will be left in a zombie state until
1242 # the parent wait on it, which we cannot do since we expect a long
1242 # the parent wait on it, which we cannot do since we expect a long
1243 # running process on success. Instead we listen for SIGCHLD telling
1243 # running process on success. Instead we listen for SIGCHLD telling
1244 # us our child process terminated.
1244 # us our child process terminated.
1245 terminated = set()
1245 terminated = set()
1246 def handler(signum, frame):
1246 def handler(signum, frame):
1247 terminated.add(os.wait())
1247 terminated.add(os.wait())
1248 prevhandler = None
1248 prevhandler = None
1249 SIGCHLD = getattr(signal, 'SIGCHLD', None)
1249 SIGCHLD = getattr(signal, 'SIGCHLD', None)
1250 if SIGCHLD is not None:
1250 if SIGCHLD is not None:
1251 prevhandler = signal.signal(SIGCHLD, handler)
1251 prevhandler = signal.signal(SIGCHLD, handler)
1252 try:
1252 try:
1253 pid = spawndetached(args)
1253 pid = spawndetached(args)
1254 while not condfn():
1254 while not condfn():
1255 if ((pid in terminated or not testpid(pid))
1255 if ((pid in terminated or not testpid(pid))
1256 and not condfn()):
1256 and not condfn()):
1257 return -1
1257 return -1
1258 time.sleep(0.1)
1258 time.sleep(0.1)
1259 return pid
1259 return pid
1260 finally:
1260 finally:
1261 if prevhandler is not None:
1261 if prevhandler is not None:
1262 signal.signal(signal.SIGCHLD, prevhandler)
1262 signal.signal(signal.SIGCHLD, prevhandler)
1263
1263
1264 try:
1264 try:
1265 any, all = any, all
1265 any, all = any, all
1266 except NameError:
1266 except NameError:
1267 def any(iterable):
1267 def any(iterable):
1268 for i in iterable:
1268 for i in iterable:
1269 if i:
1269 if i:
1270 return True
1270 return True
1271 return False
1271 return False
1272
1272
1273 def all(iterable):
1273 def all(iterable):
1274 for i in iterable:
1274 for i in iterable:
1275 if not i:
1275 if not i:
1276 return False
1276 return False
1277 return True
1277 return True
1278
1278
1279 def interpolate(prefix, mapping, s, fn=None, escape_prefix=False):
1279 def interpolate(prefix, mapping, s, fn=None, escape_prefix=False):
1280 """Return the result of interpolating items in the mapping into string s.
1280 """Return the result of interpolating items in the mapping into string s.
1281
1281
1282 prefix is a single character string, or a two character string with
1282 prefix is a single character string, or a two character string with
1283 a backslash as the first character if the prefix needs to be escaped in
1283 a backslash as the first character if the prefix needs to be escaped in
1284 a regular expression.
1284 a regular expression.
1285
1285
1286 fn is an optional function that will be applied to the replacement text
1286 fn is an optional function that will be applied to the replacement text
1287 just before replacement.
1287 just before replacement.
1288
1288
1289 escape_prefix is an optional flag that allows using doubled prefix for
1289 escape_prefix is an optional flag that allows using doubled prefix for
1290 its escaping.
1290 its escaping.
1291 """
1291 """
1292 fn = fn or (lambda s: s)
1292 fn = fn or (lambda s: s)
1293 patterns = '|'.join(mapping.keys())
1293 patterns = '|'.join(mapping.keys())
1294 if escape_prefix:
1294 if escape_prefix:
1295 patterns += '|' + prefix
1295 patterns += '|' + prefix
1296 if len(prefix) > 1:
1296 if len(prefix) > 1:
1297 prefix_char = prefix[1:]
1297 prefix_char = prefix[1:]
1298 else:
1298 else:
1299 prefix_char = prefix
1299 prefix_char = prefix
1300 mapping[prefix_char] = prefix_char
1300 mapping[prefix_char] = prefix_char
1301 r = re.compile(r'%s(%s)' % (prefix, patterns))
1301 r = re.compile(r'%s(%s)' % (prefix, patterns))
1302 return r.sub(lambda x: fn(mapping[x.group()[1:]]), s)
1302 return r.sub(lambda x: fn(mapping[x.group()[1:]]), s)
1303
1303
1304 def getport(port):
1304 def getport(port):
1305 """Return the port for a given network service.
1305 """Return the port for a given network service.
1306
1306
1307 If port is an integer, it's returned as is. If it's a string, it's
1307 If port is an integer, it's returned as is. If it's a string, it's
1308 looked up using socket.getservbyname(). If there's no matching
1308 looked up using socket.getservbyname(). If there's no matching
1309 service, util.Abort is raised.
1309 service, util.Abort is raised.
1310 """
1310 """
1311 try:
1311 try:
1312 return int(port)
1312 return int(port)
1313 except ValueError:
1313 except ValueError:
1314 pass
1314 pass
1315
1315
1316 try:
1316 try:
1317 return socket.getservbyname(port)
1317 return socket.getservbyname(port)
1318 except socket.error:
1318 except socket.error:
1319 raise Abort(_("no port number associated with service '%s'") % port)
1319 raise Abort(_("no port number associated with service '%s'") % port)
1320
1320
1321 _booleans = {'1': True, 'yes': True, 'true': True, 'on': True, 'always': True,
1321 _booleans = {'1': True, 'yes': True, 'true': True, 'on': True, 'always': True,
1322 '0': False, 'no': False, 'false': False, 'off': False,
1322 '0': False, 'no': False, 'false': False, 'off': False,
1323 'never': False}
1323 'never': False}
1324
1324
1325 def parsebool(s):
1325 def parsebool(s):
1326 """Parse s into a boolean.
1326 """Parse s into a boolean.
1327
1327
1328 If s is not a valid boolean, returns None.
1328 If s is not a valid boolean, returns None.
1329 """
1329 """
1330 return _booleans.get(s.lower(), None)
1330 return _booleans.get(s.lower(), None)
1331
1331
1332 _hexdig = '0123456789ABCDEFabcdef'
1332 _hexdig = '0123456789ABCDEFabcdef'
1333 _hextochr = dict((a + b, chr(int(a + b, 16)))
1333 _hextochr = dict((a + b, chr(int(a + b, 16)))
1334 for a in _hexdig for b in _hexdig)
1334 for a in _hexdig for b in _hexdig)
1335
1335
1336 def _urlunquote(s):
1336 def _urlunquote(s):
1337 """unquote('abc%20def') -> 'abc def'."""
1337 """unquote('abc%20def') -> 'abc def'."""
1338 res = s.split('%')
1338 res = s.split('%')
1339 # fastpath
1339 # fastpath
1340 if len(res) == 1:
1340 if len(res) == 1:
1341 return s
1341 return s
1342 s = res[0]
1342 s = res[0]
1343 for item in res[1:]:
1343 for item in res[1:]:
1344 try:
1344 try:
1345 s += _hextochr[item[:2]] + item[2:]
1345 s += _hextochr[item[:2]] + item[2:]
1346 except KeyError:
1346 except KeyError:
1347 s += '%' + item
1347 s += '%' + item
1348 except UnicodeDecodeError:
1348 except UnicodeDecodeError:
1349 s += unichr(int(item[:2], 16)) + item[2:]
1349 s += unichr(int(item[:2], 16)) + item[2:]
1350 return s
1350 return s
1351
1351
1352 class url(object):
1352 class url(object):
1353 r"""Reliable URL parser.
1353 r"""Reliable URL parser.
1354
1354
1355 This parses URLs and provides attributes for the following
1355 This parses URLs and provides attributes for the following
1356 components:
1356 components:
1357
1357
1358 <scheme>://<user>:<passwd>@<host>:<port>/<path>?<query>#<fragment>
1358 <scheme>://<user>:<passwd>@<host>:<port>/<path>?<query>#<fragment>
1359
1359
1360 Missing components are set to None. The only exception is
1360 Missing components are set to None. The only exception is
1361 fragment, which is set to '' if present but empty.
1361 fragment, which is set to '' if present but empty.
1362
1362
1363 If parsefragment is False, fragment is included in query. If
1363 If parsefragment is False, fragment is included in query. If
1364 parsequery is False, query is included in path. If both are
1364 parsequery is False, query is included in path. If both are
1365 False, both fragment and query are included in path.
1365 False, both fragment and query are included in path.
1366
1366
1367 See http://www.ietf.org/rfc/rfc2396.txt for more information.
1367 See http://www.ietf.org/rfc/rfc2396.txt for more information.
1368
1368
1369 Note that for backward compatibility reasons, bundle URLs do not
1369 Note that for backward compatibility reasons, bundle URLs do not
1370 take host names. That means 'bundle://../' has a path of '../'.
1370 take host names. That means 'bundle://../' has a path of '../'.
1371
1371
1372 Examples:
1372 Examples:
1373
1373
1374 >>> url('http://www.ietf.org/rfc/rfc2396.txt')
1374 >>> url('http://www.ietf.org/rfc/rfc2396.txt')
1375 <url scheme: 'http', host: 'www.ietf.org', path: 'rfc/rfc2396.txt'>
1375 <url scheme: 'http', host: 'www.ietf.org', path: 'rfc/rfc2396.txt'>
1376 >>> url('ssh://[::1]:2200//home/joe/repo')
1376 >>> url('ssh://[::1]:2200//home/joe/repo')
1377 <url scheme: 'ssh', host: '[::1]', port: '2200', path: '/home/joe/repo'>
1377 <url scheme: 'ssh', host: '[::1]', port: '2200', path: '/home/joe/repo'>
1378 >>> url('file:///home/joe/repo')
1378 >>> url('file:///home/joe/repo')
1379 <url scheme: 'file', path: '/home/joe/repo'>
1379 <url scheme: 'file', path: '/home/joe/repo'>
1380 >>> url('file:///c:/temp/foo/')
1380 >>> url('file:///c:/temp/foo/')
1381 <url scheme: 'file', path: 'c:/temp/foo/'>
1381 <url scheme: 'file', path: 'c:/temp/foo/'>
1382 >>> url('bundle:foo')
1382 >>> url('bundle:foo')
1383 <url scheme: 'bundle', path: 'foo'>
1383 <url scheme: 'bundle', path: 'foo'>
1384 >>> url('bundle://../foo')
1384 >>> url('bundle://../foo')
1385 <url scheme: 'bundle', path: '../foo'>
1385 <url scheme: 'bundle', path: '../foo'>
1386 >>> url(r'c:\foo\bar')
1386 >>> url(r'c:\foo\bar')
1387 <url path: 'c:\\foo\\bar'>
1387 <url path: 'c:\\foo\\bar'>
1388 >>> url(r'\\blah\blah\blah')
1388 >>> url(r'\\blah\blah\blah')
1389 <url path: '\\\\blah\\blah\\blah'>
1389 <url path: '\\\\blah\\blah\\blah'>
1390
1390
1391 Authentication credentials:
1391 Authentication credentials:
1392
1392
1393 >>> url('ssh://joe:xyz@x/repo')
1393 >>> url('ssh://joe:xyz@x/repo')
1394 <url scheme: 'ssh', user: 'joe', passwd: 'xyz', host: 'x', path: 'repo'>
1394 <url scheme: 'ssh', user: 'joe', passwd: 'xyz', host: 'x', path: 'repo'>
1395 >>> url('ssh://joe@x/repo')
1395 >>> url('ssh://joe@x/repo')
1396 <url scheme: 'ssh', user: 'joe', host: 'x', path: 'repo'>
1396 <url scheme: 'ssh', user: 'joe', host: 'x', path: 'repo'>
1397
1397
1398 Query strings and fragments:
1398 Query strings and fragments:
1399
1399
1400 >>> url('http://host/a?b#c')
1400 >>> url('http://host/a?b#c')
1401 <url scheme: 'http', host: 'host', path: 'a', query: 'b', fragment: 'c'>
1401 <url scheme: 'http', host: 'host', path: 'a', query: 'b', fragment: 'c'>
1402 >>> url('http://host/a?b#c', parsequery=False, parsefragment=False)
1402 >>> url('http://host/a?b#c', parsequery=False, parsefragment=False)
1403 <url scheme: 'http', host: 'host', path: 'a?b#c'>
1403 <url scheme: 'http', host: 'host', path: 'a?b#c'>
1404 """
1404 """
1405
1405
1406 _safechars = "!~*'()+"
1406 _safechars = "!~*'()+"
1407 _safepchars = "/!~*'()+"
1407 _safepchars = "/!~*'()+"
1408 _matchscheme = re.compile(r'^[a-zA-Z0-9+.\-]+:').match
1408 _matchscheme = re.compile(r'^[a-zA-Z0-9+.\-]+:').match
1409
1409
1410 def __init__(self, path, parsequery=True, parsefragment=True):
1410 def __init__(self, path, parsequery=True, parsefragment=True):
1411 # We slowly chomp away at path until we have only the path left
1411 # We slowly chomp away at path until we have only the path left
1412 self.scheme = self.user = self.passwd = self.host = None
1412 self.scheme = self.user = self.passwd = self.host = None
1413 self.port = self.path = self.query = self.fragment = None
1413 self.port = self.path = self.query = self.fragment = None
1414 self._localpath = True
1414 self._localpath = True
1415 self._hostport = ''
1415 self._hostport = ''
1416 self._origpath = path
1416 self._origpath = path
1417
1417
1418 # special case for Windows drive letters and UNC paths
1418 # special case for Windows drive letters and UNC paths
1419 if hasdriveletter(path) or path.startswith(r'\\'):
1419 if hasdriveletter(path) or path.startswith(r'\\'):
1420 self.path = path
1420 self.path = path
1421 return
1421 return
1422
1422
1423 # For compatibility reasons, we can't handle bundle paths as
1423 # For compatibility reasons, we can't handle bundle paths as
1424 # normal URLS
1424 # normal URLS
1425 if path.startswith('bundle:'):
1425 if path.startswith('bundle:'):
1426 self.scheme = 'bundle'
1426 self.scheme = 'bundle'
1427 path = path[7:]
1427 path = path[7:]
1428 if path.startswith('//'):
1428 if path.startswith('//'):
1429 path = path[2:]
1429 path = path[2:]
1430 self.path = path
1430 self.path = path
1431 return
1431 return
1432
1432
1433 if self._matchscheme(path):
1433 if self._matchscheme(path):
1434 parts = path.split(':', 1)
1434 parts = path.split(':', 1)
1435 if parts[0]:
1435 if parts[0]:
1436 self.scheme, path = parts
1436 self.scheme, path = parts
1437 self._localpath = False
1437 self._localpath = False
1438
1438
1439 if not path:
1439 if not path:
1440 path = None
1440 path = None
1441 if self._localpath:
1441 if self._localpath:
1442 self.path = ''
1442 self.path = ''
1443 return
1443 return
1444 else:
1444 else:
1445 if parsefragment and '#' in path:
1445 if parsefragment and '#' in path:
1446 path, self.fragment = path.split('#', 1)
1446 path, self.fragment = path.split('#', 1)
1447 if not path:
1447 if not path:
1448 path = None
1448 path = None
1449 if self._localpath:
1449 if self._localpath:
1450 self.path = path
1450 self.path = path
1451 return
1451 return
1452
1452
1453 if parsequery and '?' in path:
1453 if parsequery and '?' in path:
1454 path, self.query = path.split('?', 1)
1454 path, self.query = path.split('?', 1)
1455 if not path:
1455 if not path:
1456 path = None
1456 path = None
1457 if not self.query:
1457 if not self.query:
1458 self.query = None
1458 self.query = None
1459
1459
1460 # // is required to specify a host/authority
1460 # // is required to specify a host/authority
1461 if path and path.startswith('//'):
1461 if path and path.startswith('//'):
1462 parts = path[2:].split('/', 1)
1462 parts = path[2:].split('/', 1)
1463 if len(parts) > 1:
1463 if len(parts) > 1:
1464 self.host, path = parts
1464 self.host, path = parts
1465 path = path
1465 path = path
1466 else:
1466 else:
1467 self.host = parts[0]
1467 self.host = parts[0]
1468 path = None
1468 path = None
1469 if not self.host:
1469 if not self.host:
1470 self.host = None
1470 self.host = None
1471 # path of file:///d is /d
1472 # path of file:///d:/ is d:/, not /d:/
1471 if path and not hasdriveletter(path):
1473 if path and not hasdriveletter(path):
1472 path = '/' + path
1474 path = '/' + path
1473
1475
1474 if self.host and '@' in self.host:
1476 if self.host and '@' in self.host:
1475 self.user, self.host = self.host.rsplit('@', 1)
1477 self.user, self.host = self.host.rsplit('@', 1)
1476 if ':' in self.user:
1478 if ':' in self.user:
1477 self.user, self.passwd = self.user.split(':', 1)
1479 self.user, self.passwd = self.user.split(':', 1)
1478 if not self.host:
1480 if not self.host:
1479 self.host = None
1481 self.host = None
1480
1482
1481 # Don't split on colons in IPv6 addresses without ports
1483 # Don't split on colons in IPv6 addresses without ports
1482 if (self.host and ':' in self.host and
1484 if (self.host and ':' in self.host and
1483 not (self.host.startswith('[') and self.host.endswith(']'))):
1485 not (self.host.startswith('[') and self.host.endswith(']'))):
1484 self._hostport = self.host
1486 self._hostport = self.host
1485 self.host, self.port = self.host.rsplit(':', 1)
1487 self.host, self.port = self.host.rsplit(':', 1)
1486 if not self.host:
1488 if not self.host:
1487 self.host = None
1489 self.host = None
1488
1490
1489 if (self.host and self.scheme == 'file' and
1491 if (self.host and self.scheme == 'file' and
1490 self.host not in ('localhost', '127.0.0.1', '[::1]')):
1492 self.host not in ('localhost', '127.0.0.1', '[::1]')):
1491 raise Abort(_('file:// URLs can only refer to localhost'))
1493 raise Abort(_('file:// URLs can only refer to localhost'))
1492
1494
1493 self.path = path
1495 self.path = path
1494
1496
1495 # leave the query string escaped
1497 # leave the query string escaped
1496 for a in ('user', 'passwd', 'host', 'port',
1498 for a in ('user', 'passwd', 'host', 'port',
1497 'path', 'fragment'):
1499 'path', 'fragment'):
1498 v = getattr(self, a)
1500 v = getattr(self, a)
1499 if v is not None:
1501 if v is not None:
1500 setattr(self, a, _urlunquote(v))
1502 setattr(self, a, _urlunquote(v))
1501
1503
1502 def __repr__(self):
1504 def __repr__(self):
1503 attrs = []
1505 attrs = []
1504 for a in ('scheme', 'user', 'passwd', 'host', 'port', 'path',
1506 for a in ('scheme', 'user', 'passwd', 'host', 'port', 'path',
1505 'query', 'fragment'):
1507 'query', 'fragment'):
1506 v = getattr(self, a)
1508 v = getattr(self, a)
1507 if v is not None:
1509 if v is not None:
1508 attrs.append('%s: %r' % (a, v))
1510 attrs.append('%s: %r' % (a, v))
1509 return '<url %s>' % ', '.join(attrs)
1511 return '<url %s>' % ', '.join(attrs)
1510
1512
1511 def __str__(self):
1513 def __str__(self):
1512 r"""Join the URL's components back into a URL string.
1514 r"""Join the URL's components back into a URL string.
1513
1515
1514 Examples:
1516 Examples:
1515
1517
1516 >>> str(url('http://user:pw@host:80/?foo#bar'))
1518 >>> str(url('http://user:pw@host:80/?foo#bar'))
1517 'http://user:pw@host:80/?foo#bar'
1519 'http://user:pw@host:80/?foo#bar'
1518 >>> str(url('http://user:pw@host:80/?foo=bar&baz=42'))
1520 >>> str(url('http://user:pw@host:80/?foo=bar&baz=42'))
1519 'http://user:pw@host:80/?foo=bar&baz=42'
1521 'http://user:pw@host:80/?foo=bar&baz=42'
1520 >>> str(url('http://user:pw@host:80/?foo=bar%3dbaz'))
1522 >>> str(url('http://user:pw@host:80/?foo=bar%3dbaz'))
1521 'http://user:pw@host:80/?foo=bar%3dbaz'
1523 'http://user:pw@host:80/?foo=bar%3dbaz'
1522 >>> str(url('ssh://user:pw@[::1]:2200//home/joe#'))
1524 >>> str(url('ssh://user:pw@[::1]:2200//home/joe#'))
1523 'ssh://user:pw@[::1]:2200//home/joe#'
1525 'ssh://user:pw@[::1]:2200//home/joe#'
1524 >>> str(url('http://localhost:80//'))
1526 >>> str(url('http://localhost:80//'))
1525 'http://localhost:80//'
1527 'http://localhost:80//'
1526 >>> str(url('http://localhost:80/'))
1528 >>> str(url('http://localhost:80/'))
1527 'http://localhost:80/'
1529 'http://localhost:80/'
1528 >>> str(url('http://localhost:80'))
1530 >>> str(url('http://localhost:80'))
1529 'http://localhost:80/'
1531 'http://localhost:80/'
1530 >>> str(url('bundle:foo'))
1532 >>> str(url('bundle:foo'))
1531 'bundle:foo'
1533 'bundle:foo'
1532 >>> str(url('bundle://../foo'))
1534 >>> str(url('bundle://../foo'))
1533 'bundle:../foo'
1535 'bundle:../foo'
1534 >>> str(url('path'))
1536 >>> str(url('path'))
1535 'path'
1537 'path'
1536 >>> str(url('file:///tmp/foo/bar'))
1538 >>> str(url('file:///tmp/foo/bar'))
1537 'file:///tmp/foo/bar'
1539 'file:///tmp/foo/bar'
1538 >>> print url(r'bundle:foo\bar')
1540 >>> print url(r'bundle:foo\bar')
1539 bundle:foo\bar
1541 bundle:foo\bar
1540 """
1542 """
1541 if self._localpath:
1543 if self._localpath:
1542 s = self.path
1544 s = self.path
1543 if self.scheme == 'bundle':
1545 if self.scheme == 'bundle':
1544 s = 'bundle:' + s
1546 s = 'bundle:' + s
1545 if self.fragment:
1547 if self.fragment:
1546 s += '#' + self.fragment
1548 s += '#' + self.fragment
1547 return s
1549 return s
1548
1550
1549 s = self.scheme + ':'
1551 s = self.scheme + ':'
1550 if self.user or self.passwd or self.host:
1552 if self.user or self.passwd or self.host:
1551 s += '//'
1553 s += '//'
1552 elif self.scheme and (not self.path or self.path.startswith('/')):
1554 elif self.scheme and (not self.path or self.path.startswith('/')):
1553 s += '//'
1555 s += '//'
1554 if self.user:
1556 if self.user:
1555 s += urllib.quote(self.user, safe=self._safechars)
1557 s += urllib.quote(self.user, safe=self._safechars)
1556 if self.passwd:
1558 if self.passwd:
1557 s += ':' + urllib.quote(self.passwd, safe=self._safechars)
1559 s += ':' + urllib.quote(self.passwd, safe=self._safechars)
1558 if self.user or self.passwd:
1560 if self.user or self.passwd:
1559 s += '@'
1561 s += '@'
1560 if self.host:
1562 if self.host:
1561 if not (self.host.startswith('[') and self.host.endswith(']')):
1563 if not (self.host.startswith('[') and self.host.endswith(']')):
1562 s += urllib.quote(self.host)
1564 s += urllib.quote(self.host)
1563 else:
1565 else:
1564 s += self.host
1566 s += self.host
1565 if self.port:
1567 if self.port:
1566 s += ':' + urllib.quote(self.port)
1568 s += ':' + urllib.quote(self.port)
1567 if self.host:
1569 if self.host:
1568 s += '/'
1570 s += '/'
1569 if self.path:
1571 if self.path:
1570 # TODO: similar to the query string, we should not unescape the
1572 # TODO: similar to the query string, we should not unescape the
1571 # path when we store it, the path might contain '%2f' = '/',
1573 # path when we store it, the path might contain '%2f' = '/',
1572 # which we should *not* escape.
1574 # which we should *not* escape.
1573 s += urllib.quote(self.path, safe=self._safepchars)
1575 s += urllib.quote(self.path, safe=self._safepchars)
1574 if self.query:
1576 if self.query:
1575 # we store the query in escaped form.
1577 # we store the query in escaped form.
1576 s += '?' + self.query
1578 s += '?' + self.query
1577 if self.fragment is not None:
1579 if self.fragment is not None:
1578 s += '#' + urllib.quote(self.fragment, safe=self._safepchars)
1580 s += '#' + urllib.quote(self.fragment, safe=self._safepchars)
1579 return s
1581 return s
1580
1582
1581 def authinfo(self):
1583 def authinfo(self):
1582 user, passwd = self.user, self.passwd
1584 user, passwd = self.user, self.passwd
1583 try:
1585 try:
1584 self.user, self.passwd = None, None
1586 self.user, self.passwd = None, None
1585 s = str(self)
1587 s = str(self)
1586 finally:
1588 finally:
1587 self.user, self.passwd = user, passwd
1589 self.user, self.passwd = user, passwd
1588 if not self.user:
1590 if not self.user:
1589 return (s, None)
1591 return (s, None)
1590 return (s, (None, (str(self), self.host),
1592 # authinfo[1] is passed to urllib2 password manager, and its URIs
1593 # must not contain credentials.
1594 return (s, (None, (s, self.host),
1591 self.user, self.passwd or ''))
1595 self.user, self.passwd or ''))
1592
1596
1593 def isabs(self):
1597 def isabs(self):
1594 if self.scheme and self.scheme != 'file':
1598 if self.scheme and self.scheme != 'file':
1595 return True # remote URL
1599 return True # remote URL
1596 if hasdriveletter(self.path):
1600 if hasdriveletter(self.path):
1597 return True # absolute for our purposes - can't be joined()
1601 return True # absolute for our purposes - can't be joined()
1598 if self.path.startswith(r'\\'):
1602 if self.path.startswith(r'\\'):
1599 return True # Windows UNC path
1603 return True # Windows UNC path
1600 if self.path.startswith('/'):
1604 if self.path.startswith('/'):
1601 return True # POSIX-style
1605 return True # POSIX-style
1602 return False
1606 return False
1603
1607
1604 def localpath(self):
1608 def localpath(self):
1605 if self.scheme == 'file' or self.scheme == 'bundle':
1609 if self.scheme == 'file' or self.scheme == 'bundle':
1606 path = self.path or '/'
1610 path = self.path or '/'
1607 # For Windows, we need to promote hosts containing drive
1611 # For Windows, we need to promote hosts containing drive
1608 # letters to paths with drive letters.
1612 # letters to paths with drive letters.
1609 if hasdriveletter(self._hostport):
1613 if hasdriveletter(self._hostport):
1610 path = self._hostport + '/' + self.path
1614 path = self._hostport + '/' + self.path
1611 elif self.host is not None and self.path:
1615 elif self.host is not None and self.path:
1612 path = '/' + path
1616 path = '/' + path
1613 # We also need to handle the case of file:///C:/, which
1614 # should return C:/, not /C:/.
1615 elif hasdriveletter(path):
1616 # Strip leading slash from paths with drive names
1617 return path[1:]
1618 return path
1617 return path
1619 return self._origpath
1618 return self._origpath
1620
1619
1621 def hasscheme(path):
1620 def hasscheme(path):
1622 return bool(url(path).scheme)
1621 return bool(url(path).scheme)
1623
1622
1624 def hasdriveletter(path):
1623 def hasdriveletter(path):
1625 return path[1:2] == ':' and path[0:1].isalpha()
1624 return path[1:2] == ':' and path[0:1].isalpha()
1626
1625
1627 def urllocalpath(path):
1626 def urllocalpath(path):
1628 return url(path, parsequery=False, parsefragment=False).localpath()
1627 return url(path, parsequery=False, parsefragment=False).localpath()
1629
1628
1630 def hidepassword(u):
1629 def hidepassword(u):
1631 '''hide user credential in a url string'''
1630 '''hide user credential in a url string'''
1632 u = url(u)
1631 u = url(u)
1633 if u.passwd:
1632 if u.passwd:
1634 u.passwd = '***'
1633 u.passwd = '***'
1635 return str(u)
1634 return str(u)
1636
1635
1637 def removeauth(u):
1636 def removeauth(u):
1638 '''remove all authentication information from a url string'''
1637 '''remove all authentication information from a url string'''
1639 u = url(u)
1638 u = url(u)
1640 u.user = u.passwd = None
1639 u.user = u.passwd = None
1641 return str(u)
1640 return str(u)
1642
1641
1643 def isatty(fd):
1642 def isatty(fd):
1644 try:
1643 try:
1645 return fd.isatty()
1644 return fd.isatty()
1646 except AttributeError:
1645 except AttributeError:
1647 return False
1646 return False
@@ -1,97 +1,107 b''
1 from mercurial import demandimport; demandimport.enable()
1 from mercurial import demandimport; demandimport.enable()
2 import urllib2
2 from mercurial import ui, util
3 from mercurial import ui, util
3 from mercurial import url
4 from mercurial import url
4 from mercurial.error import Abort
5 from mercurial.error import Abort
5
6
6 class myui(ui.ui):
7 class myui(ui.ui):
7 def interactive(self):
8 def interactive(self):
8 return False
9 return False
9
10
10 origui = myui()
11 origui = myui()
11
12
12 def writeauth(items):
13 def writeauth(items):
13 ui = origui.copy()
14 ui = origui.copy()
14 for name, value in items.iteritems():
15 for name, value in items.iteritems():
15 ui.setconfig('auth', name, value)
16 ui.setconfig('auth', name, value)
16 return ui
17 return ui
17
18
18 def dumpdict(dict):
19 def dumpdict(dict):
19 return '{' + ', '.join(['%s: %s' % (k, dict[k])
20 return '{' + ', '.join(['%s: %s' % (k, dict[k])
20 for k in sorted(dict.iterkeys())]) + '}'
21 for k in sorted(dict.iterkeys())]) + '}'
21
22
22 def test(auth, urls=None):
23 def test(auth, urls=None):
23 print 'CFG:', dumpdict(auth)
24 print 'CFG:', dumpdict(auth)
24 prefixes = set()
25 prefixes = set()
25 for k in auth:
26 for k in auth:
26 prefixes.add(k.split('.', 1)[0])
27 prefixes.add(k.split('.', 1)[0])
27 for p in prefixes:
28 for p in prefixes:
28 for name in ('.username', '.password'):
29 for name in ('.username', '.password'):
29 if (p + name) not in auth:
30 if (p + name) not in auth:
30 auth[p + name] = p
31 auth[p + name] = p
31 auth = dict((k, v) for k, v in auth.iteritems() if v is not None)
32 auth = dict((k, v) for k, v in auth.iteritems() if v is not None)
32
33
33 ui = writeauth(auth)
34 ui = writeauth(auth)
34
35
35 def _test(uri):
36 def _test(uri):
36 print 'URI:', uri
37 print 'URI:', uri
37 try:
38 try:
38 pm = url.passwordmgr(ui)
39 pm = url.passwordmgr(ui)
39 authinfo = util.url(uri).authinfo()[1]
40 u, authinfo = util.url(uri).authinfo()
40 if authinfo is not None:
41 if authinfo is not None:
41 pm.add_password(*authinfo)
42 pm.add_password(*authinfo)
42 print ' ', pm.find_user_password('test', uri)
43 print ' ', pm.find_user_password('test', u)
43 except Abort, e:
44 except Abort, e:
44 print 'abort'
45 print 'abort'
45
46
46 if not urls:
47 if not urls:
47 urls = [
48 urls = [
48 'http://example.org/foo',
49 'http://example.org/foo',
49 'http://example.org/foo/bar',
50 'http://example.org/foo/bar',
50 'http://example.org/bar',
51 'http://example.org/bar',
51 'https://example.org/foo',
52 'https://example.org/foo',
52 'https://example.org/foo/bar',
53 'https://example.org/foo/bar',
53 'https://example.org/bar',
54 'https://example.org/bar',
54 'https://x@example.org/bar',
55 'https://x@example.org/bar',
55 'https://y@example.org/bar',
56 'https://y@example.org/bar',
56 ]
57 ]
57 for u in urls:
58 for u in urls:
58 _test(u)
59 _test(u)
59
60
60
61
61 print '\n*** Test in-uri schemes\n'
62 print '\n*** Test in-uri schemes\n'
62 test({'x.prefix': 'http://example.org'})
63 test({'x.prefix': 'http://example.org'})
63 test({'x.prefix': 'https://example.org'})
64 test({'x.prefix': 'https://example.org'})
64 test({'x.prefix': 'http://example.org', 'x.schemes': 'https'})
65 test({'x.prefix': 'http://example.org', 'x.schemes': 'https'})
65 test({'x.prefix': 'https://example.org', 'x.schemes': 'http'})
66 test({'x.prefix': 'https://example.org', 'x.schemes': 'http'})
66
67
67 print '\n*** Test separately configured schemes\n'
68 print '\n*** Test separately configured schemes\n'
68 test({'x.prefix': 'example.org', 'x.schemes': 'http'})
69 test({'x.prefix': 'example.org', 'x.schemes': 'http'})
69 test({'x.prefix': 'example.org', 'x.schemes': 'https'})
70 test({'x.prefix': 'example.org', 'x.schemes': 'https'})
70 test({'x.prefix': 'example.org', 'x.schemes': 'http https'})
71 test({'x.prefix': 'example.org', 'x.schemes': 'http https'})
71
72
72 print '\n*** Test prefix matching\n'
73 print '\n*** Test prefix matching\n'
73 test({'x.prefix': 'http://example.org/foo',
74 test({'x.prefix': 'http://example.org/foo',
74 'y.prefix': 'http://example.org/bar'})
75 'y.prefix': 'http://example.org/bar'})
75 test({'x.prefix': 'http://example.org/foo',
76 test({'x.prefix': 'http://example.org/foo',
76 'y.prefix': 'http://example.org/foo/bar'})
77 'y.prefix': 'http://example.org/foo/bar'})
77 test({'x.prefix': '*', 'y.prefix': 'https://example.org/bar'})
78 test({'x.prefix': '*', 'y.prefix': 'https://example.org/bar'})
78
79
79 print '\n*** Test user matching\n'
80 print '\n*** Test user matching\n'
80 test({'x.prefix': 'http://example.org/foo',
81 test({'x.prefix': 'http://example.org/foo',
81 'x.username': None,
82 'x.username': None,
82 'x.password': 'xpassword'},
83 'x.password': 'xpassword'},
83 urls=['http://y@example.org/foo'])
84 urls=['http://y@example.org/foo'])
84 test({'x.prefix': 'http://example.org/foo',
85 test({'x.prefix': 'http://example.org/foo',
85 'x.username': None,
86 'x.username': None,
86 'x.password': 'xpassword',
87 'x.password': 'xpassword',
87 'y.prefix': 'http://example.org/foo',
88 'y.prefix': 'http://example.org/foo',
88 'y.username': 'y',
89 'y.username': 'y',
89 'y.password': 'ypassword'},
90 'y.password': 'ypassword'},
90 urls=['http://y@example.org/foo'])
91 urls=['http://y@example.org/foo'])
91 test({'x.prefix': 'http://example.org/foo/bar',
92 test({'x.prefix': 'http://example.org/foo/bar',
92 'x.username': None,
93 'x.username': None,
93 'x.password': 'xpassword',
94 'x.password': 'xpassword',
94 'y.prefix': 'http://example.org/foo',
95 'y.prefix': 'http://example.org/foo',
95 'y.username': 'y',
96 'y.username': 'y',
96 'y.password': 'ypassword'},
97 'y.password': 'ypassword'},
97 urls=['http://y@example.org/foo/bar'])
98 urls=['http://y@example.org/foo/bar'])
99
100 def testauthinfo(fullurl, authurl):
101 print 'URIs:', fullurl, authurl
102 pm = urllib2.HTTPPasswordMgrWithDefaultRealm()
103 pm.add_password(*util.url(fullurl).authinfo()[1])
104 print pm.find_user_password('test', authurl)
105
106 print '\n*** Test urllib2 and util.url\n'
107 testauthinfo('http://user@example.com:8080/foo', 'http://example.com:8080/foo')
@@ -1,191 +1,196 b''
1
1
2 *** Test in-uri schemes
2 *** Test in-uri schemes
3
3
4 CFG: {x.prefix: http://example.org}
4 CFG: {x.prefix: http://example.org}
5 URI: http://example.org/foo
5 URI: http://example.org/foo
6 ('x', 'x')
6 ('x', 'x')
7 URI: http://example.org/foo/bar
7 URI: http://example.org/foo/bar
8 ('x', 'x')
8 ('x', 'x')
9 URI: http://example.org/bar
9 URI: http://example.org/bar
10 ('x', 'x')
10 ('x', 'x')
11 URI: https://example.org/foo
11 URI: https://example.org/foo
12 abort
12 abort
13 URI: https://example.org/foo/bar
13 URI: https://example.org/foo/bar
14 abort
14 abort
15 URI: https://example.org/bar
15 URI: https://example.org/bar
16 abort
16 abort
17 URI: https://x@example.org/bar
17 URI: https://x@example.org/bar
18 abort
18 abort
19 URI: https://y@example.org/bar
19 URI: https://y@example.org/bar
20 abort
20 abort
21 CFG: {x.prefix: https://example.org}
21 CFG: {x.prefix: https://example.org}
22 URI: http://example.org/foo
22 URI: http://example.org/foo
23 abort
23 abort
24 URI: http://example.org/foo/bar
24 URI: http://example.org/foo/bar
25 abort
25 abort
26 URI: http://example.org/bar
26 URI: http://example.org/bar
27 abort
27 abort
28 URI: https://example.org/foo
28 URI: https://example.org/foo
29 ('x', 'x')
29 ('x', 'x')
30 URI: https://example.org/foo/bar
30 URI: https://example.org/foo/bar
31 ('x', 'x')
31 ('x', 'x')
32 URI: https://example.org/bar
32 URI: https://example.org/bar
33 ('x', 'x')
33 ('x', 'x')
34 URI: https://x@example.org/bar
34 URI: https://x@example.org/bar
35 ('x', 'x')
35 ('x', 'x')
36 URI: https://y@example.org/bar
36 URI: https://y@example.org/bar
37 abort
37 abort
38 CFG: {x.prefix: http://example.org, x.schemes: https}
38 CFG: {x.prefix: http://example.org, x.schemes: https}
39 URI: http://example.org/foo
39 URI: http://example.org/foo
40 ('x', 'x')
40 ('x', 'x')
41 URI: http://example.org/foo/bar
41 URI: http://example.org/foo/bar
42 ('x', 'x')
42 ('x', 'x')
43 URI: http://example.org/bar
43 URI: http://example.org/bar
44 ('x', 'x')
44 ('x', 'x')
45 URI: https://example.org/foo
45 URI: https://example.org/foo
46 abort
46 abort
47 URI: https://example.org/foo/bar
47 URI: https://example.org/foo/bar
48 abort
48 abort
49 URI: https://example.org/bar
49 URI: https://example.org/bar
50 abort
50 abort
51 URI: https://x@example.org/bar
51 URI: https://x@example.org/bar
52 abort
52 abort
53 URI: https://y@example.org/bar
53 URI: https://y@example.org/bar
54 abort
54 abort
55 CFG: {x.prefix: https://example.org, x.schemes: http}
55 CFG: {x.prefix: https://example.org, x.schemes: http}
56 URI: http://example.org/foo
56 URI: http://example.org/foo
57 abort
57 abort
58 URI: http://example.org/foo/bar
58 URI: http://example.org/foo/bar
59 abort
59 abort
60 URI: http://example.org/bar
60 URI: http://example.org/bar
61 abort
61 abort
62 URI: https://example.org/foo
62 URI: https://example.org/foo
63 ('x', 'x')
63 ('x', 'x')
64 URI: https://example.org/foo/bar
64 URI: https://example.org/foo/bar
65 ('x', 'x')
65 ('x', 'x')
66 URI: https://example.org/bar
66 URI: https://example.org/bar
67 ('x', 'x')
67 ('x', 'x')
68 URI: https://x@example.org/bar
68 URI: https://x@example.org/bar
69 ('x', 'x')
69 ('x', 'x')
70 URI: https://y@example.org/bar
70 URI: https://y@example.org/bar
71 abort
71 abort
72
72
73 *** Test separately configured schemes
73 *** Test separately configured schemes
74
74
75 CFG: {x.prefix: example.org, x.schemes: http}
75 CFG: {x.prefix: example.org, x.schemes: http}
76 URI: http://example.org/foo
76 URI: http://example.org/foo
77 ('x', 'x')
77 ('x', 'x')
78 URI: http://example.org/foo/bar
78 URI: http://example.org/foo/bar
79 ('x', 'x')
79 ('x', 'x')
80 URI: http://example.org/bar
80 URI: http://example.org/bar
81 ('x', 'x')
81 ('x', 'x')
82 URI: https://example.org/foo
82 URI: https://example.org/foo
83 abort
83 abort
84 URI: https://example.org/foo/bar
84 URI: https://example.org/foo/bar
85 abort
85 abort
86 URI: https://example.org/bar
86 URI: https://example.org/bar
87 abort
87 abort
88 URI: https://x@example.org/bar
88 URI: https://x@example.org/bar
89 abort
89 abort
90 URI: https://y@example.org/bar
90 URI: https://y@example.org/bar
91 abort
91 abort
92 CFG: {x.prefix: example.org, x.schemes: https}
92 CFG: {x.prefix: example.org, x.schemes: https}
93 URI: http://example.org/foo
93 URI: http://example.org/foo
94 abort
94 abort
95 URI: http://example.org/foo/bar
95 URI: http://example.org/foo/bar
96 abort
96 abort
97 URI: http://example.org/bar
97 URI: http://example.org/bar
98 abort
98 abort
99 URI: https://example.org/foo
99 URI: https://example.org/foo
100 ('x', 'x')
100 ('x', 'x')
101 URI: https://example.org/foo/bar
101 URI: https://example.org/foo/bar
102 ('x', 'x')
102 ('x', 'x')
103 URI: https://example.org/bar
103 URI: https://example.org/bar
104 ('x', 'x')
104 ('x', 'x')
105 URI: https://x@example.org/bar
105 URI: https://x@example.org/bar
106 ('x', 'x')
106 ('x', 'x')
107 URI: https://y@example.org/bar
107 URI: https://y@example.org/bar
108 abort
108 abort
109 CFG: {x.prefix: example.org, x.schemes: http https}
109 CFG: {x.prefix: example.org, x.schemes: http https}
110 URI: http://example.org/foo
110 URI: http://example.org/foo
111 ('x', 'x')
111 ('x', 'x')
112 URI: http://example.org/foo/bar
112 URI: http://example.org/foo/bar
113 ('x', 'x')
113 ('x', 'x')
114 URI: http://example.org/bar
114 URI: http://example.org/bar
115 ('x', 'x')
115 ('x', 'x')
116 URI: https://example.org/foo
116 URI: https://example.org/foo
117 ('x', 'x')
117 ('x', 'x')
118 URI: https://example.org/foo/bar
118 URI: https://example.org/foo/bar
119 ('x', 'x')
119 ('x', 'x')
120 URI: https://example.org/bar
120 URI: https://example.org/bar
121 ('x', 'x')
121 ('x', 'x')
122 URI: https://x@example.org/bar
122 URI: https://x@example.org/bar
123 ('x', 'x')
123 ('x', 'x')
124 URI: https://y@example.org/bar
124 URI: https://y@example.org/bar
125 abort
125 abort
126
126
127 *** Test prefix matching
127 *** Test prefix matching
128
128
129 CFG: {x.prefix: http://example.org/foo, y.prefix: http://example.org/bar}
129 CFG: {x.prefix: http://example.org/foo, y.prefix: http://example.org/bar}
130 URI: http://example.org/foo
130 URI: http://example.org/foo
131 ('x', 'x')
131 ('x', 'x')
132 URI: http://example.org/foo/bar
132 URI: http://example.org/foo/bar
133 ('x', 'x')
133 ('x', 'x')
134 URI: http://example.org/bar
134 URI: http://example.org/bar
135 ('y', 'y')
135 ('y', 'y')
136 URI: https://example.org/foo
136 URI: https://example.org/foo
137 abort
137 abort
138 URI: https://example.org/foo/bar
138 URI: https://example.org/foo/bar
139 abort
139 abort
140 URI: https://example.org/bar
140 URI: https://example.org/bar
141 abort
141 abort
142 URI: https://x@example.org/bar
142 URI: https://x@example.org/bar
143 abort
143 abort
144 URI: https://y@example.org/bar
144 URI: https://y@example.org/bar
145 abort
145 abort
146 CFG: {x.prefix: http://example.org/foo, y.prefix: http://example.org/foo/bar}
146 CFG: {x.prefix: http://example.org/foo, y.prefix: http://example.org/foo/bar}
147 URI: http://example.org/foo
147 URI: http://example.org/foo
148 ('x', 'x')
148 ('x', 'x')
149 URI: http://example.org/foo/bar
149 URI: http://example.org/foo/bar
150 ('y', 'y')
150 ('y', 'y')
151 URI: http://example.org/bar
151 URI: http://example.org/bar
152 abort
152 abort
153 URI: https://example.org/foo
153 URI: https://example.org/foo
154 abort
154 abort
155 URI: https://example.org/foo/bar
155 URI: https://example.org/foo/bar
156 abort
156 abort
157 URI: https://example.org/bar
157 URI: https://example.org/bar
158 abort
158 abort
159 URI: https://x@example.org/bar
159 URI: https://x@example.org/bar
160 abort
160 abort
161 URI: https://y@example.org/bar
161 URI: https://y@example.org/bar
162 abort
162 abort
163 CFG: {x.prefix: *, y.prefix: https://example.org/bar}
163 CFG: {x.prefix: *, y.prefix: https://example.org/bar}
164 URI: http://example.org/foo
164 URI: http://example.org/foo
165 abort
165 abort
166 URI: http://example.org/foo/bar
166 URI: http://example.org/foo/bar
167 abort
167 abort
168 URI: http://example.org/bar
168 URI: http://example.org/bar
169 abort
169 abort
170 URI: https://example.org/foo
170 URI: https://example.org/foo
171 ('x', 'x')
171 ('x', 'x')
172 URI: https://example.org/foo/bar
172 URI: https://example.org/foo/bar
173 ('x', 'x')
173 ('x', 'x')
174 URI: https://example.org/bar
174 URI: https://example.org/bar
175 ('y', 'y')
175 ('y', 'y')
176 URI: https://x@example.org/bar
176 URI: https://x@example.org/bar
177 ('x', 'x')
177 ('x', 'x')
178 URI: https://y@example.org/bar
178 URI: https://y@example.org/bar
179 ('y', 'y')
179 ('y', 'y')
180
180
181 *** Test user matching
181 *** Test user matching
182
182
183 CFG: {x.password: xpassword, x.prefix: http://example.org/foo, x.username: None}
183 CFG: {x.password: xpassword, x.prefix: http://example.org/foo, x.username: None}
184 URI: http://y@example.org/foo
184 URI: http://y@example.org/foo
185 ('y', 'xpassword')
185 ('y', 'xpassword')
186 CFG: {x.password: xpassword, x.prefix: http://example.org/foo, x.username: None, y.password: ypassword, y.prefix: http://example.org/foo, y.username: y}
186 CFG: {x.password: xpassword, x.prefix: http://example.org/foo, x.username: None, y.password: ypassword, y.prefix: http://example.org/foo, y.username: y}
187 URI: http://y@example.org/foo
187 URI: http://y@example.org/foo
188 ('y', 'ypassword')
188 ('y', 'ypassword')
189 CFG: {x.password: xpassword, x.prefix: http://example.org/foo/bar, x.username: None, y.password: ypassword, y.prefix: http://example.org/foo, y.username: y}
189 CFG: {x.password: xpassword, x.prefix: http://example.org/foo/bar, x.username: None, y.password: ypassword, y.prefix: http://example.org/foo, y.username: y}
190 URI: http://y@example.org/foo/bar
190 URI: http://y@example.org/foo/bar
191 ('y', 'xpassword')
191 ('y', 'xpassword')
192
193 *** Test urllib2 and util.url
194
195 URIs: http://user@example.com:8080/foo http://example.com:8080/foo
196 ('user', '')
@@ -1,115 +1,164 b''
1
1
2 $ hg init test
2 $ hg init test
3 $ cd test
3 $ cd test
4 $ echo foo>foo
4 $ echo foo>foo
5 $ mkdir foo.d foo.d/bAr.hg.d foo.d/baR.d.hg
5 $ mkdir foo.d foo.d/bAr.hg.d foo.d/baR.d.hg
6 $ echo foo>foo.d/foo
6 $ echo foo>foo.d/foo
7 $ echo bar>foo.d/bAr.hg.d/BaR
7 $ echo bar>foo.d/bAr.hg.d/BaR
8 $ echo bar>foo.d/baR.d.hg/bAR
8 $ echo bar>foo.d/baR.d.hg/bAR
9 $ hg commit -A -m 1
9 $ hg commit -A -m 1
10 adding foo
10 adding foo
11 adding foo.d/bAr.hg.d/BaR
11 adding foo.d/bAr.hg.d/BaR
12 adding foo.d/baR.d.hg/bAR
12 adding foo.d/baR.d.hg/bAR
13 adding foo.d/foo
13 adding foo.d/foo
14 $ hg serve -p $HGPORT -d --pid-file=../hg1.pid -E ../error.log
14 $ hg serve -p $HGPORT -d --pid-file=../hg1.pid -E ../error.log
15 $ hg --config server.uncompressed=False serve -p $HGPORT1 -d --pid-file=../hg2.pid
15 $ hg --config server.uncompressed=False serve -p $HGPORT1 -d --pid-file=../hg2.pid
16
16
17 Test server address cannot be reused
17 Test server address cannot be reused
18
18
19 $ hg serve -p $HGPORT1 2>&1
19 $ hg serve -p $HGPORT1 2>&1
20 abort: cannot start server at ':$HGPORT1': Address already in use
20 abort: cannot start server at ':$HGPORT1': Address already in use
21 [255]
21 [255]
22 $ cd ..
22 $ cd ..
23 $ cat hg1.pid hg2.pid >> $DAEMON_PIDS
23 $ cat hg1.pid hg2.pid >> $DAEMON_PIDS
24
24
25 clone via stream
25 clone via stream
26
26
27 $ hg clone --uncompressed http://localhost:$HGPORT/ copy 2>&1
27 $ hg clone --uncompressed http://localhost:$HGPORT/ copy 2>&1
28 streaming all changes
28 streaming all changes
29 6 files to transfer, 606 bytes of data
29 6 files to transfer, 606 bytes of data
30 transferred * bytes in * seconds (*/sec) (glob)
30 transferred * bytes in * seconds (*/sec) (glob)
31 updating to branch default
31 updating to branch default
32 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
32 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
33 $ hg verify -R copy
33 $ hg verify -R copy
34 checking changesets
34 checking changesets
35 checking manifests
35 checking manifests
36 crosschecking files in changesets and manifests
36 crosschecking files in changesets and manifests
37 checking files
37 checking files
38 4 files, 1 changesets, 4 total revisions
38 4 files, 1 changesets, 4 total revisions
39
39
40 try to clone via stream, should use pull instead
40 try to clone via stream, should use pull instead
41
41
42 $ hg clone --uncompressed http://localhost:$HGPORT1/ copy2
42 $ hg clone --uncompressed http://localhost:$HGPORT1/ copy2
43 requesting all changes
43 requesting all changes
44 adding changesets
44 adding changesets
45 adding manifests
45 adding manifests
46 adding file changes
46 adding file changes
47 added 1 changesets with 4 changes to 4 files
47 added 1 changesets with 4 changes to 4 files
48 updating to branch default
48 updating to branch default
49 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
49 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
50
50
51 clone via pull
51 clone via pull
52
52
53 $ hg clone http://localhost:$HGPORT1/ copy-pull
53 $ hg clone http://localhost:$HGPORT1/ copy-pull
54 requesting all changes
54 requesting all changes
55 adding changesets
55 adding changesets
56 adding manifests
56 adding manifests
57 adding file changes
57 adding file changes
58 added 1 changesets with 4 changes to 4 files
58 added 1 changesets with 4 changes to 4 files
59 updating to branch default
59 updating to branch default
60 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
60 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
61 $ hg verify -R copy-pull
61 $ hg verify -R copy-pull
62 checking changesets
62 checking changesets
63 checking manifests
63 checking manifests
64 crosschecking files in changesets and manifests
64 crosschecking files in changesets and manifests
65 checking files
65 checking files
66 4 files, 1 changesets, 4 total revisions
66 4 files, 1 changesets, 4 total revisions
67 $ cd test
67 $ cd test
68 $ echo bar > bar
68 $ echo bar > bar
69 $ hg commit -A -d '1 0' -m 2
69 $ hg commit -A -d '1 0' -m 2
70 adding bar
70 adding bar
71 $ cd ..
71 $ cd ..
72
72
73 incoming via HTTP
73 incoming via HTTP
74
74
75 $ hg clone http://localhost:$HGPORT1/ --rev 0 partial
75 $ hg clone http://localhost:$HGPORT1/ --rev 0 partial
76 adding changesets
76 adding changesets
77 adding manifests
77 adding manifests
78 adding file changes
78 adding file changes
79 added 1 changesets with 4 changes to 4 files
79 added 1 changesets with 4 changes to 4 files
80 updating to branch default
80 updating to branch default
81 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
81 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
82 $ cd partial
82 $ cd partial
83 $ touch LOCAL
83 $ touch LOCAL
84 $ hg ci -qAm LOCAL
84 $ hg ci -qAm LOCAL
85 $ hg incoming http://localhost:$HGPORT1/ --template '{desc}\n'
85 $ hg incoming http://localhost:$HGPORT1/ --template '{desc}\n'
86 comparing with http://localhost:$HGPORT1/
86 comparing with http://localhost:$HGPORT1/
87 searching for changes
87 searching for changes
88 2
88 2
89 $ cd ..
89 $ cd ..
90
90
91 pull
91 pull
92
92
93 $ cd copy-pull
93 $ cd copy-pull
94 $ echo '[hooks]' >> .hg/hgrc
94 $ echo '[hooks]' >> .hg/hgrc
95 $ echo 'changegroup = python "$TESTDIR"/printenv.py changegroup' >> .hg/hgrc
95 $ echo 'changegroup = python "$TESTDIR"/printenv.py changegroup' >> .hg/hgrc
96 $ hg pull
96 $ hg pull
97 pulling from http://localhost:$HGPORT1/
97 pulling from http://localhost:$HGPORT1/
98 searching for changes
98 searching for changes
99 adding changesets
99 adding changesets
100 adding manifests
100 adding manifests
101 adding file changes
101 adding file changes
102 added 1 changesets with 1 changes to 1 files
102 added 1 changesets with 1 changes to 1 files
103 changegroup hook: HG_NODE=5fed3813f7f5e1824344fdc9cf8f63bb662c292d HG_SOURCE=pull HG_URL=http://localhost:$HGPORT1/
103 changegroup hook: HG_NODE=5fed3813f7f5e1824344fdc9cf8f63bb662c292d HG_SOURCE=pull HG_URL=http://localhost:$HGPORT1/
104 (run 'hg update' to get a working copy)
104 (run 'hg update' to get a working copy)
105 $ cd ..
105 $ cd ..
106
106
107 clone from invalid URL
107 clone from invalid URL
108
108
109 $ hg clone http://localhost:$HGPORT/bad
109 $ hg clone http://localhost:$HGPORT/bad
110 abort: HTTP Error 404: Not Found
110 abort: HTTP Error 404: Not Found
111 [255]
111 [255]
112
112
113 test http authentication
114
115 $ cd test
116 $ cat << EOT > userpass.py
117 > import base64
118 > from mercurial.hgweb import common
119 > def perform_authentication(hgweb, req, op):
120 > auth = req.env.get('HTTP_AUTHORIZATION')
121 > if not auth:
122 > raise common.ErrorResponse(common.HTTP_UNAUTHORIZED, 'who',
123 > [('WWW-Authenticate', 'Basic Realm="mercurial"')])
124 > if base64.b64decode(auth.split()[1]).split(':', 1) != ['user', 'pass']:
125 > raise common.ErrorResponse(common.HTTP_FORBIDDEN, 'no')
126 > def extsetup():
127 > common.permhooks.insert(0, perform_authentication)
128 > EOT
129 $ hg --config extensions.x=userpass.py serve -p $HGPORT2 -d --pid-file=pid
130 $ cat pid >> $DAEMON_PIDS
131
132 $ hg id http://localhost:$HGPORT2/
133 abort: http authorization required
134 [255]
135 $ hg id http://user@localhost:$HGPORT2/
136 abort: http authorization required
137 [255]
138 $ hg id http://user:pass@localhost:$HGPORT2/
139 5fed3813f7f5
140 $ echo '[auth]' >> .hg/hgrc
141 $ echo 'l.schemes=http' >> .hg/hgrc
142 $ echo 'l.prefix=lo' >> .hg/hgrc
143 $ echo 'l.username=user' >> .hg/hgrc
144 $ echo 'l.password=pass' >> .hg/hgrc
145 $ hg id http://localhost:$HGPORT2/
146 5fed3813f7f5
147 $ hg id http://localhost:$HGPORT2/
148 5fed3813f7f5
149 $ hg id http://user@localhost:$HGPORT2/
150 5fed3813f7f5
151 $ hg id http://user:pass@localhost:$HGPORT2/
152 5fed3813f7f5
153 $ hg id http://user2@localhost:$HGPORT2/
154 abort: http authorization required
155 [255]
156 $ hg id http://user:pass2@localhost:$HGPORT2/
157 abort: HTTP Error 403: no
158 [255]
159
160 $ cd ..
161
113 check error log
162 check error log
114
163
115 $ cat error.log
164 $ cat error.log
@@ -1,221 +1,235 b''
1 import sys
1 import sys
2
2
3 def check(a, b):
3 def check(a, b):
4 if a != b:
4 if a != b:
5 print (a, b)
5 print (a, b)
6
6
7 def cert(cn):
7 def cert(cn):
8 return dict(subject=((('commonName', cn),),))
8 return dict(subject=((('commonName', cn),),))
9
9
10 from mercurial.sslutil import _verifycert
10 from mercurial.sslutil import _verifycert
11
11
12 # Test non-wildcard certificates
12 # Test non-wildcard certificates
13 check(_verifycert(cert('example.com'), 'example.com'),
13 check(_verifycert(cert('example.com'), 'example.com'),
14 None)
14 None)
15 check(_verifycert(cert('example.com'), 'www.example.com'),
15 check(_verifycert(cert('example.com'), 'www.example.com'),
16 'certificate is for example.com')
16 'certificate is for example.com')
17 check(_verifycert(cert('www.example.com'), 'example.com'),
17 check(_verifycert(cert('www.example.com'), 'example.com'),
18 'certificate is for www.example.com')
18 'certificate is for www.example.com')
19
19
20 # Test wildcard certificates
20 # Test wildcard certificates
21 check(_verifycert(cert('*.example.com'), 'www.example.com'),
21 check(_verifycert(cert('*.example.com'), 'www.example.com'),
22 None)
22 None)
23 check(_verifycert(cert('*.example.com'), 'example.com'),
23 check(_verifycert(cert('*.example.com'), 'example.com'),
24 'certificate is for *.example.com')
24 'certificate is for *.example.com')
25 check(_verifycert(cert('*.example.com'), 'w.w.example.com'),
25 check(_verifycert(cert('*.example.com'), 'w.w.example.com'),
26 'certificate is for *.example.com')
26 'certificate is for *.example.com')
27
27
28 # Test subjectAltName
28 # Test subjectAltName
29 san_cert = {'subject': ((('commonName', 'example.com'),),),
29 san_cert = {'subject': ((('commonName', 'example.com'),),),
30 'subjectAltName': (('DNS', '*.example.net'),
30 'subjectAltName': (('DNS', '*.example.net'),
31 ('DNS', 'example.net'))}
31 ('DNS', 'example.net'))}
32 check(_verifycert(san_cert, 'example.net'),
32 check(_verifycert(san_cert, 'example.net'),
33 None)
33 None)
34 check(_verifycert(san_cert, 'foo.example.net'),
34 check(_verifycert(san_cert, 'foo.example.net'),
35 None)
35 None)
36 # no fallback to subject commonName when subjectAltName has DNS
36 # no fallback to subject commonName when subjectAltName has DNS
37 check(_verifycert(san_cert, 'example.com'),
37 check(_verifycert(san_cert, 'example.com'),
38 'certificate is for *.example.net, example.net')
38 'certificate is for *.example.net, example.net')
39 # fallback to subject commonName when no DNS in subjectAltName
39 # fallback to subject commonName when no DNS in subjectAltName
40 san_cert = {'subject': ((('commonName', 'example.com'),),),
40 san_cert = {'subject': ((('commonName', 'example.com'),),),
41 'subjectAltName': (('IP Address', '8.8.8.8'),)}
41 'subjectAltName': (('IP Address', '8.8.8.8'),)}
42 check(_verifycert(san_cert, 'example.com'), None)
42 check(_verifycert(san_cert, 'example.com'), None)
43
43
44 # Avoid some pitfalls
44 # Avoid some pitfalls
45 check(_verifycert(cert('*.foo'), 'foo'),
45 check(_verifycert(cert('*.foo'), 'foo'),
46 'certificate is for *.foo')
46 'certificate is for *.foo')
47 check(_verifycert(cert('*o'), 'foo'),
47 check(_verifycert(cert('*o'), 'foo'),
48 'certificate is for *o')
48 'certificate is for *o')
49
49
50 check(_verifycert({'subject': ()},
50 check(_verifycert({'subject': ()},
51 'example.com'),
51 'example.com'),
52 'no commonName or subjectAltName found in certificate')
52 'no commonName or subjectAltName found in certificate')
53 check(_verifycert(None, 'example.com'),
53 check(_verifycert(None, 'example.com'),
54 'no certificate received')
54 'no certificate received')
55
55
56 # Unicode (IDN) certname isn't supported
56 # Unicode (IDN) certname isn't supported
57 check(_verifycert(cert(u'\u4f8b.jp'), 'example.jp'),
57 check(_verifycert(cert(u'\u4f8b.jp'), 'example.jp'),
58 'IDN in certificate not supported')
58 'IDN in certificate not supported')
59
59
60 import doctest
60 import doctest
61
61
62 def test_url():
62 def test_url():
63 """
63 """
64 >>> from mercurial.util import url
64 >>> from mercurial.util import url
65
65
66 This tests for edge cases in url.URL's parsing algorithm. Most of
66 This tests for edge cases in url.URL's parsing algorithm. Most of
67 these aren't useful for documentation purposes, so they aren't
67 these aren't useful for documentation purposes, so they aren't
68 part of the class's doc tests.
68 part of the class's doc tests.
69
69
70 Query strings and fragments:
70 Query strings and fragments:
71
71
72 >>> url('http://host/a?b#c')
72 >>> url('http://host/a?b#c')
73 <url scheme: 'http', host: 'host', path: 'a', query: 'b', fragment: 'c'>
73 <url scheme: 'http', host: 'host', path: 'a', query: 'b', fragment: 'c'>
74 >>> url('http://host/a?')
74 >>> url('http://host/a?')
75 <url scheme: 'http', host: 'host', path: 'a'>
75 <url scheme: 'http', host: 'host', path: 'a'>
76 >>> url('http://host/a#b#c')
76 >>> url('http://host/a#b#c')
77 <url scheme: 'http', host: 'host', path: 'a', fragment: 'b#c'>
77 <url scheme: 'http', host: 'host', path: 'a', fragment: 'b#c'>
78 >>> url('http://host/a#b?c')
78 >>> url('http://host/a#b?c')
79 <url scheme: 'http', host: 'host', path: 'a', fragment: 'b?c'>
79 <url scheme: 'http', host: 'host', path: 'a', fragment: 'b?c'>
80 >>> url('http://host/?a#b')
80 >>> url('http://host/?a#b')
81 <url scheme: 'http', host: 'host', path: '', query: 'a', fragment: 'b'>
81 <url scheme: 'http', host: 'host', path: '', query: 'a', fragment: 'b'>
82 >>> url('http://host/?a#b', parsequery=False)
82 >>> url('http://host/?a#b', parsequery=False)
83 <url scheme: 'http', host: 'host', path: '?a', fragment: 'b'>
83 <url scheme: 'http', host: 'host', path: '?a', fragment: 'b'>
84 >>> url('http://host/?a#b', parsefragment=False)
84 >>> url('http://host/?a#b', parsefragment=False)
85 <url scheme: 'http', host: 'host', path: '', query: 'a#b'>
85 <url scheme: 'http', host: 'host', path: '', query: 'a#b'>
86 >>> url('http://host/?a#b', parsequery=False, parsefragment=False)
86 >>> url('http://host/?a#b', parsequery=False, parsefragment=False)
87 <url scheme: 'http', host: 'host', path: '?a#b'>
87 <url scheme: 'http', host: 'host', path: '?a#b'>
88
88
89 IPv6 addresses:
89 IPv6 addresses:
90
90
91 >>> url('ldap://[2001:db8::7]/c=GB?objectClass?one')
91 >>> url('ldap://[2001:db8::7]/c=GB?objectClass?one')
92 <url scheme: 'ldap', host: '[2001:db8::7]', path: 'c=GB',
92 <url scheme: 'ldap', host: '[2001:db8::7]', path: 'c=GB',
93 query: 'objectClass?one'>
93 query: 'objectClass?one'>
94 >>> url('ldap://joe:xxx@[2001:db8::7]:80/c=GB?objectClass?one')
94 >>> url('ldap://joe:xxx@[2001:db8::7]:80/c=GB?objectClass?one')
95 <url scheme: 'ldap', user: 'joe', passwd: 'xxx', host: '[2001:db8::7]',
95 <url scheme: 'ldap', user: 'joe', passwd: 'xxx', host: '[2001:db8::7]',
96 port: '80', path: 'c=GB', query: 'objectClass?one'>
96 port: '80', path: 'c=GB', query: 'objectClass?one'>
97
97
98 Missing scheme, host, etc.:
98 Missing scheme, host, etc.:
99
99
100 >>> url('://192.0.2.16:80/')
100 >>> url('://192.0.2.16:80/')
101 <url path: '://192.0.2.16:80/'>
101 <url path: '://192.0.2.16:80/'>
102 >>> url('http://mercurial.selenic.com')
102 >>> url('http://mercurial.selenic.com')
103 <url scheme: 'http', host: 'mercurial.selenic.com'>
103 <url scheme: 'http', host: 'mercurial.selenic.com'>
104 >>> url('/foo')
104 >>> url('/foo')
105 <url path: '/foo'>
105 <url path: '/foo'>
106 >>> url('bundle:/foo')
106 >>> url('bundle:/foo')
107 <url scheme: 'bundle', path: '/foo'>
107 <url scheme: 'bundle', path: '/foo'>
108 >>> url('a?b#c')
108 >>> url('a?b#c')
109 <url path: 'a?b', fragment: 'c'>
109 <url path: 'a?b', fragment: 'c'>
110 >>> url('http://x.com?arg=/foo')
110 >>> url('http://x.com?arg=/foo')
111 <url scheme: 'http', host: 'x.com', query: 'arg=/foo'>
111 <url scheme: 'http', host: 'x.com', query: 'arg=/foo'>
112 >>> url('http://joe:xxx@/foo')
112 >>> url('http://joe:xxx@/foo')
113 <url scheme: 'http', user: 'joe', passwd: 'xxx', path: 'foo'>
113 <url scheme: 'http', user: 'joe', passwd: 'xxx', path: 'foo'>
114
114
115 Just a scheme and a path:
115 Just a scheme and a path:
116
116
117 >>> url('mailto:John.Doe@example.com')
117 >>> url('mailto:John.Doe@example.com')
118 <url scheme: 'mailto', path: 'John.Doe@example.com'>
118 <url scheme: 'mailto', path: 'John.Doe@example.com'>
119 >>> url('a:b:c:d')
119 >>> url('a:b:c:d')
120 <url path: 'a:b:c:d'>
120 <url path: 'a:b:c:d'>
121 >>> url('aa:bb:cc:dd')
121 >>> url('aa:bb:cc:dd')
122 <url scheme: 'aa', path: 'bb:cc:dd'>
122 <url scheme: 'aa', path: 'bb:cc:dd'>
123
123
124 SSH examples:
124 SSH examples:
125
125
126 >>> url('ssh://joe@host//home/joe')
126 >>> url('ssh://joe@host//home/joe')
127 <url scheme: 'ssh', user: 'joe', host: 'host', path: '/home/joe'>
127 <url scheme: 'ssh', user: 'joe', host: 'host', path: '/home/joe'>
128 >>> url('ssh://joe:xxx@host/src')
128 >>> url('ssh://joe:xxx@host/src')
129 <url scheme: 'ssh', user: 'joe', passwd: 'xxx', host: 'host', path: 'src'>
129 <url scheme: 'ssh', user: 'joe', passwd: 'xxx', host: 'host', path: 'src'>
130 >>> url('ssh://joe:xxx@host')
130 >>> url('ssh://joe:xxx@host')
131 <url scheme: 'ssh', user: 'joe', passwd: 'xxx', host: 'host'>
131 <url scheme: 'ssh', user: 'joe', passwd: 'xxx', host: 'host'>
132 >>> url('ssh://joe@host')
132 >>> url('ssh://joe@host')
133 <url scheme: 'ssh', user: 'joe', host: 'host'>
133 <url scheme: 'ssh', user: 'joe', host: 'host'>
134 >>> url('ssh://host')
134 >>> url('ssh://host')
135 <url scheme: 'ssh', host: 'host'>
135 <url scheme: 'ssh', host: 'host'>
136 >>> url('ssh://')
136 >>> url('ssh://')
137 <url scheme: 'ssh'>
137 <url scheme: 'ssh'>
138 >>> url('ssh:')
138 >>> url('ssh:')
139 <url scheme: 'ssh'>
139 <url scheme: 'ssh'>
140
140
141 Non-numeric port:
141 Non-numeric port:
142
142
143 >>> url('http://example.com:dd')
143 >>> url('http://example.com:dd')
144 <url scheme: 'http', host: 'example.com', port: 'dd'>
144 <url scheme: 'http', host: 'example.com', port: 'dd'>
145 >>> url('ssh://joe:xxx@host:ssh/foo')
145 >>> url('ssh://joe:xxx@host:ssh/foo')
146 <url scheme: 'ssh', user: 'joe', passwd: 'xxx', host: 'host', port: 'ssh',
146 <url scheme: 'ssh', user: 'joe', passwd: 'xxx', host: 'host', port: 'ssh',
147 path: 'foo'>
147 path: 'foo'>
148
148
149 Bad authentication credentials:
149 Bad authentication credentials:
150
150
151 >>> url('http://joe@joeville:123@4:@host/a?b#c')
151 >>> url('http://joe@joeville:123@4:@host/a?b#c')
152 <url scheme: 'http', user: 'joe@joeville', passwd: '123@4:',
152 <url scheme: 'http', user: 'joe@joeville', passwd: '123@4:',
153 host: 'host', path: 'a', query: 'b', fragment: 'c'>
153 host: 'host', path: 'a', query: 'b', fragment: 'c'>
154 >>> url('http://!*#?/@!*#?/:@host/a?b#c')
154 >>> url('http://!*#?/@!*#?/:@host/a?b#c')
155 <url scheme: 'http', host: '!*', fragment: '?/@!*#?/:@host/a?b#c'>
155 <url scheme: 'http', host: '!*', fragment: '?/@!*#?/:@host/a?b#c'>
156 >>> url('http://!*#?@!*#?:@host/a?b#c')
156 >>> url('http://!*#?@!*#?:@host/a?b#c')
157 <url scheme: 'http', host: '!*', fragment: '?@!*#?:@host/a?b#c'>
157 <url scheme: 'http', host: '!*', fragment: '?@!*#?:@host/a?b#c'>
158 >>> url('http://!*@:!*@@host/a?b#c')
158 >>> url('http://!*@:!*@@host/a?b#c')
159 <url scheme: 'http', user: '!*@', passwd: '!*@', host: 'host',
159 <url scheme: 'http', user: '!*@', passwd: '!*@', host: 'host',
160 path: 'a', query: 'b', fragment: 'c'>
160 path: 'a', query: 'b', fragment: 'c'>
161
161
162 File paths:
162 File paths:
163
163
164 >>> url('a/b/c/d.g.f')
164 >>> url('a/b/c/d.g.f')
165 <url path: 'a/b/c/d.g.f'>
165 <url path: 'a/b/c/d.g.f'>
166 >>> url('/x///z/y/')
166 >>> url('/x///z/y/')
167 <url path: '/x///z/y/'>
167 <url path: '/x///z/y/'>
168 >>> url('/foo:bar')
168 >>> url('/foo:bar')
169 <url path: '/foo:bar'>
169 <url path: '/foo:bar'>
170 >>> url('\\\\foo:bar')
170 >>> url('\\\\foo:bar')
171 <url path: '\\\\foo:bar'>
171 <url path: '\\\\foo:bar'>
172 >>> url('./foo:bar')
172 >>> url('./foo:bar')
173 <url path: './foo:bar'>
173 <url path: './foo:bar'>
174
174
175 Non-localhost file URL:
175 Non-localhost file URL:
176
176
177 >>> u = url('file://mercurial.selenic.com/foo')
177 >>> u = url('file://mercurial.selenic.com/foo')
178 Traceback (most recent call last):
178 Traceback (most recent call last):
179 File "<stdin>", line 1, in ?
179 File "<stdin>", line 1, in ?
180 Abort: file:// URLs can only refer to localhost
180 Abort: file:// URLs can only refer to localhost
181
181
182 Empty URL:
182 Empty URL:
183
183
184 >>> u = url('')
184 >>> u = url('')
185 >>> u
185 >>> u
186 <url path: ''>
186 <url path: ''>
187 >>> str(u)
187 >>> str(u)
188 ''
188 ''
189
189
190 Empty path with query string:
190 Empty path with query string:
191
191
192 >>> str(url('http://foo/?bar'))
192 >>> str(url('http://foo/?bar'))
193 'http://foo/?bar'
193 'http://foo/?bar'
194
194
195 Invalid path:
195 Invalid path:
196
196
197 >>> u = url('http://foo/bar')
197 >>> u = url('http://foo/bar')
198 >>> u.path = 'bar'
198 >>> u.path = 'bar'
199 >>> str(u)
199 >>> str(u)
200 'http://foo/bar'
200 'http://foo/bar'
201
201
202 >>> u = url('file:/foo/bar/baz')
202 >>> u = url('file:/foo/bar/baz')
203 >>> u
203 >>> u
204 <url scheme: 'file', path: '/foo/bar/baz'>
204 <url scheme: 'file', path: '/foo/bar/baz'>
205 >>> str(u)
205 >>> str(u)
206 'file:///foo/bar/baz'
206 'file:///foo/bar/baz'
207 >>> u.localpath()
208 '/foo/bar/baz'
207
209
208 >>> u = url('file:///foo/bar/baz')
210 >>> u = url('file:///foo/bar/baz')
209 >>> u
211 >>> u
210 <url scheme: 'file', path: '/foo/bar/baz'>
212 <url scheme: 'file', path: '/foo/bar/baz'>
211 >>> str(u)
213 >>> str(u)
212 'file:///foo/bar/baz'
214 'file:///foo/bar/baz'
215 >>> u.localpath()
216 '/foo/bar/baz'
217
218 >>> u = url('file:///f:oo/bar/baz')
219 >>> u
220 <url scheme: 'file', path: 'f:oo/bar/baz'>
221 >>> str(u)
222 'file:f%3Aoo/bar/baz'
223 >>> u.localpath()
224 'f:oo/bar/baz'
213
225
214 >>> u = url('file:foo/bar/baz')
226 >>> u = url('file:foo/bar/baz')
215 >>> u
227 >>> u
216 <url scheme: 'file', path: 'foo/bar/baz'>
228 <url scheme: 'file', path: 'foo/bar/baz'>
217 >>> str(u)
229 >>> str(u)
218 'file:foo/bar/baz'
230 'file:foo/bar/baz'
231 >>> u.localpath()
232 'foo/bar/baz'
219 """
233 """
220
234
221 doctest.testmod(optionflags=doctest.NORMALIZE_WHITESPACE)
235 doctest.testmod(optionflags=doctest.NORMALIZE_WHITESPACE)
General Comments 0
You need to be logged in to leave comments. Login now