##// END OF EJS Templates
sshpeer: store subprocess so it cleans up correctly...
Durham Goode -
r18759:9baf4330 default
parent child Browse files
Show More
@@ -1,239 +1,242 b''
1 1 # sshpeer.py - ssh repository proxy class for mercurial
2 2 #
3 3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 import re
9 9 from i18n import _
10 10 import util, error, wireproto
11 11
12 12 class remotelock(object):
13 13 def __init__(self, repo):
14 14 self.repo = repo
15 15 def release(self):
16 16 self.repo.unlock()
17 17 self.repo = None
18 18 def __del__(self):
19 19 if self.repo:
20 20 self.release()
21 21
22 22 def _serverquote(s):
23 23 '''quote a string for the remote shell ... which we assume is sh'''
24 24 if re.match('[a-zA-Z0-9@%_+=:,./-]*$', s):
25 25 return s
26 26 return "'%s'" % s.replace("'", "'\\''")
27 27
28 28 class sshpeer(wireproto.wirepeer):
29 29 def __init__(self, ui, path, create=False):
30 30 self._url = path
31 31 self.ui = ui
32 32 self.pipeo = self.pipei = self.pipee = None
33 33
34 34 u = util.url(path, parsequery=False, parsefragment=False)
35 35 if u.scheme != 'ssh' or not u.host or u.path is None:
36 36 self._abort(error.RepoError(_("couldn't parse location %s") % path))
37 37
38 38 self.user = u.user
39 39 if u.passwd is not None:
40 40 self._abort(error.RepoError(_("password in URL not supported")))
41 41 self.host = u.host
42 42 self.port = u.port
43 43 self.path = u.path or "."
44 44
45 45 sshcmd = self.ui.config("ui", "ssh", "ssh")
46 46 remotecmd = self.ui.config("ui", "remotecmd", "hg")
47 47
48 48 args = util.sshargs(sshcmd, self.host, self.user, self.port)
49 49
50 50 if create:
51 51 cmd = '%s %s %s' % (sshcmd, args,
52 52 util.shellquote("%s init %s" %
53 53 (_serverquote(remotecmd), _serverquote(self.path))))
54 54 ui.note(_('running %s\n') % cmd)
55 55 res = util.system(cmd)
56 56 if res != 0:
57 57 self._abort(error.RepoError(_("could not create remote repo")))
58 58
59 59 self.validate_repo(ui, sshcmd, args, remotecmd)
60 60
61 61 def url(self):
62 62 return self._url
63 63
64 64 def validate_repo(self, ui, sshcmd, args, remotecmd):
65 65 # cleanup up previous run
66 66 self.cleanup()
67 67
68 68 cmd = '%s %s %s' % (sshcmd, args,
69 69 util.shellquote("%s -R %s serve --stdio" %
70 70 (_serverquote(remotecmd), _serverquote(self.path))))
71 71 ui.note(_('running %s\n') % cmd)
72 72 cmd = util.quotecommand(cmd)
73 self.pipeo, self.pipei, self.pipee = util.popen3(cmd)
73
74 # while self.subprocess isn't used, having it allows the subprocess to
75 # to clean up correctly later
76 self.pipeo, self.pipei, self.pipee, self.subprocess = util.popen4(cmd)
74 77
75 78 # skip any noise generated by remote shell
76 79 self._callstream("hello")
77 80 r = self._callstream("between", pairs=("%s-%s" % ("0"*40, "0"*40)))
78 81 lines = ["", "dummy"]
79 82 max_noise = 500
80 83 while lines[-1] and max_noise:
81 84 l = r.readline()
82 85 self.readerr()
83 86 if lines[-1] == "1\n" and l == "\n":
84 87 break
85 88 if l:
86 89 ui.debug("remote: ", l)
87 90 lines.append(l)
88 91 max_noise -= 1
89 92 else:
90 93 self._abort(error.RepoError(_('no suitable response from '
91 94 'remote hg')))
92 95
93 96 self._caps = set()
94 97 for l in reversed(lines):
95 98 if l.startswith("capabilities:"):
96 99 self._caps.update(l[:-1].split(":")[1].split())
97 100 break
98 101
99 102 def _capabilities(self):
100 103 return self._caps
101 104
102 105 def readerr(self):
103 106 while True:
104 107 size = util.fstat(self.pipee).st_size
105 108 if size == 0:
106 109 break
107 110 s = self.pipee.read(size)
108 111 if not s:
109 112 break
110 113 for l in s.splitlines():
111 114 self.ui.status(_("remote: "), l, '\n')
112 115
113 116 def _abort(self, exception):
114 117 self.cleanup()
115 118 raise exception
116 119
117 120 def cleanup(self):
118 121 if self.pipeo is None:
119 122 return
120 123 self.pipeo.close()
121 124 self.pipei.close()
122 125 try:
123 126 # read the error descriptor until EOF
124 127 for l in self.pipee:
125 128 self.ui.status(_("remote: "), l)
126 129 except (IOError, ValueError):
127 130 pass
128 131 self.pipee.close()
129 132
130 133 __del__ = cleanup
131 134
132 135 def _callstream(self, cmd, **args):
133 136 self.ui.debug("sending %s command\n" % cmd)
134 137 self.pipeo.write("%s\n" % cmd)
135 138 _func, names = wireproto.commands[cmd]
136 139 keys = names.split()
137 140 wireargs = {}
138 141 for k in keys:
139 142 if k == '*':
140 143 wireargs['*'] = args
141 144 break
142 145 else:
143 146 wireargs[k] = args[k]
144 147 del args[k]
145 148 for k, v in sorted(wireargs.iteritems()):
146 149 self.pipeo.write("%s %d\n" % (k, len(v)))
147 150 if isinstance(v, dict):
148 151 for dk, dv in v.iteritems():
149 152 self.pipeo.write("%s %d\n" % (dk, len(dv)))
150 153 self.pipeo.write(dv)
151 154 else:
152 155 self.pipeo.write(v)
153 156 self.pipeo.flush()
154 157
155 158 return self.pipei
156 159
157 160 def _call(self, cmd, **args):
158 161 self._callstream(cmd, **args)
159 162 return self._recv()
160 163
161 164 def _callpush(self, cmd, fp, **args):
162 165 r = self._call(cmd, **args)
163 166 if r:
164 167 return '', r
165 168 while True:
166 169 d = fp.read(4096)
167 170 if not d:
168 171 break
169 172 self._send(d)
170 173 self._send("", flush=True)
171 174 r = self._recv()
172 175 if r:
173 176 return '', r
174 177 return self._recv(), ''
175 178
176 179 def _decompress(self, stream):
177 180 return stream
178 181
179 182 def _recv(self):
180 183 l = self.pipei.readline()
181 184 if l == '\n':
182 185 err = []
183 186 while True:
184 187 line = self.pipee.readline()
185 188 if line == '-\n':
186 189 break
187 190 err.extend([line])
188 191 if len(err) > 0:
189 192 # strip the trailing newline added to the last line server-side
190 193 err[-1] = err[-1][:-1]
191 194 self._abort(error.OutOfBandError(*err))
192 195 self.readerr()
193 196 try:
194 197 l = int(l)
195 198 except ValueError:
196 199 self._abort(error.ResponseError(_("unexpected response:"), l))
197 200 return self.pipei.read(l)
198 201
199 202 def _send(self, data, flush=False):
200 203 self.pipeo.write("%d\n" % len(data))
201 204 if data:
202 205 self.pipeo.write(data)
203 206 if flush:
204 207 self.pipeo.flush()
205 208 self.readerr()
206 209
207 210 def lock(self):
208 211 self._call("lock")
209 212 return remotelock(self)
210 213
211 214 def unlock(self):
212 215 self._call("unlock")
213 216
214 217 def addchangegroup(self, cg, source, url, lock=None):
215 218 '''Send a changegroup to the remote server. Return an integer
216 219 similar to unbundle(). DEPRECATED, since it requires locking the
217 220 remote.'''
218 221 d = self._call("addchangegroup")
219 222 if d:
220 223 self._abort(error.RepoError(_("push refused: %s") % d))
221 224 while True:
222 225 d = cg.read(4096)
223 226 if not d:
224 227 break
225 228 self.pipeo.write(d)
226 229 self.readerr()
227 230
228 231 self.pipeo.flush()
229 232
230 233 self.readerr()
231 234 r = self._recv()
232 235 if not r:
233 236 return 1
234 237 try:
235 238 return int(r)
236 239 except ValueError:
237 240 self._abort(error.ResponseError(_("unexpected response:"), r))
238 241
239 242 instance = sshpeer
@@ -1,1917 +1,1921 b''
1 1 # util.py - Mercurial utility functions and platform specific implementations
2 2 #
3 3 # Copyright 2005 K. Thananchayan <thananck@yahoo.com>
4 4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
6 6 #
7 7 # This software may be used and distributed according to the terms of the
8 8 # GNU General Public License version 2 or any later version.
9 9
10 10 """Mercurial utility functions and platform specific implementations.
11 11
12 12 This contains helper routines that are independent of the SCM core and
13 13 hide platform-specific details from the core.
14 14 """
15 15
16 16 from i18n import _
17 17 import error, osutil, encoding, collections
18 18 import errno, re, shutil, sys, tempfile, traceback
19 19 import os, time, datetime, calendar, textwrap, signal
20 20 import imp, socket, urllib
21 21
22 22 if os.name == 'nt':
23 23 import windows as platform
24 24 else:
25 25 import posix as platform
26 26
27 27 cachestat = platform.cachestat
28 28 checkexec = platform.checkexec
29 29 checklink = platform.checklink
30 30 copymode = platform.copymode
31 31 executablepath = platform.executablepath
32 32 expandglobs = platform.expandglobs
33 33 explainexit = platform.explainexit
34 34 findexe = platform.findexe
35 35 gethgcmd = platform.gethgcmd
36 36 getuser = platform.getuser
37 37 groupmembers = platform.groupmembers
38 38 groupname = platform.groupname
39 39 hidewindow = platform.hidewindow
40 40 isexec = platform.isexec
41 41 isowner = platform.isowner
42 42 localpath = platform.localpath
43 43 lookupreg = platform.lookupreg
44 44 makedir = platform.makedir
45 45 nlinks = platform.nlinks
46 46 normpath = platform.normpath
47 47 normcase = platform.normcase
48 48 openhardlinks = platform.openhardlinks
49 49 oslink = platform.oslink
50 50 parsepatchoutput = platform.parsepatchoutput
51 51 pconvert = platform.pconvert
52 52 popen = platform.popen
53 53 posixfile = platform.posixfile
54 54 quotecommand = platform.quotecommand
55 55 realpath = platform.realpath
56 56 rename = platform.rename
57 57 samedevice = platform.samedevice
58 58 samefile = platform.samefile
59 59 samestat = platform.samestat
60 60 setbinary = platform.setbinary
61 61 setflags = platform.setflags
62 62 setsignalhandler = platform.setsignalhandler
63 63 shellquote = platform.shellquote
64 64 spawndetached = platform.spawndetached
65 65 split = platform.split
66 66 sshargs = platform.sshargs
67 67 statfiles = getattr(osutil, 'statfiles', platform.statfiles)
68 68 termwidth = platform.termwidth
69 69 testpid = platform.testpid
70 70 umask = platform.umask
71 71 unlink = platform.unlink
72 72 unlinkpath = platform.unlinkpath
73 73 username = platform.username
74 74
75 75 # Python compatibility
76 76
77 77 _notset = object()
78 78
79 79 def safehasattr(thing, attr):
80 80 return getattr(thing, attr, _notset) is not _notset
81 81
82 82 def sha1(s=''):
83 83 '''
84 84 Low-overhead wrapper around Python's SHA support
85 85
86 86 >>> f = _fastsha1
87 87 >>> a = sha1()
88 88 >>> a = f()
89 89 >>> a.hexdigest()
90 90 'da39a3ee5e6b4b0d3255bfef95601890afd80709'
91 91 '''
92 92
93 93 return _fastsha1(s)
94 94
95 95 def _fastsha1(s=''):
96 96 # This function will import sha1 from hashlib or sha (whichever is
97 97 # available) and overwrite itself with it on the first call.
98 98 # Subsequent calls will go directly to the imported function.
99 99 if sys.version_info >= (2, 5):
100 100 from hashlib import sha1 as _sha1
101 101 else:
102 102 from sha import sha as _sha1
103 103 global _fastsha1, sha1
104 104 _fastsha1 = sha1 = _sha1
105 105 return _sha1(s)
106 106
107 107 try:
108 108 buffer = buffer
109 109 except NameError:
110 110 if sys.version_info[0] < 3:
111 111 def buffer(sliceable, offset=0):
112 112 return sliceable[offset:]
113 113 else:
114 114 def buffer(sliceable, offset=0):
115 115 return memoryview(sliceable)[offset:]
116 116
117 117 import subprocess
118 118 closefds = os.name == 'posix'
119 119
120 120 def popen2(cmd, env=None, newlines=False):
121 121 # Setting bufsize to -1 lets the system decide the buffer size.
122 122 # The default for bufsize is 0, meaning unbuffered. This leads to
123 123 # poor performance on Mac OS X: http://bugs.python.org/issue4194
124 124 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
125 125 close_fds=closefds,
126 126 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
127 127 universal_newlines=newlines,
128 128 env=env)
129 129 return p.stdin, p.stdout
130 130
131 131 def popen3(cmd, env=None, newlines=False):
132 stdin, stdout, stderr, p = popen4(cmd, env, newlines)
133 return stdin, stdout, stderr
134
135 def popen4(cmd, env=None, newlines=False):
132 136 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
133 137 close_fds=closefds,
134 138 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
135 139 stderr=subprocess.PIPE,
136 140 universal_newlines=newlines,
137 141 env=env)
138 return p.stdin, p.stdout, p.stderr
142 return p.stdin, p.stdout, p.stderr, p
139 143
140 144 def version():
141 145 """Return version information if available."""
142 146 try:
143 147 import __version__
144 148 return __version__.version
145 149 except ImportError:
146 150 return 'unknown'
147 151
148 152 # used by parsedate
149 153 defaultdateformats = (
150 154 '%Y-%m-%d %H:%M:%S',
151 155 '%Y-%m-%d %I:%M:%S%p',
152 156 '%Y-%m-%d %H:%M',
153 157 '%Y-%m-%d %I:%M%p',
154 158 '%Y-%m-%d',
155 159 '%m-%d',
156 160 '%m/%d',
157 161 '%m/%d/%y',
158 162 '%m/%d/%Y',
159 163 '%a %b %d %H:%M:%S %Y',
160 164 '%a %b %d %I:%M:%S%p %Y',
161 165 '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
162 166 '%b %d %H:%M:%S %Y',
163 167 '%b %d %I:%M:%S%p %Y',
164 168 '%b %d %H:%M:%S',
165 169 '%b %d %I:%M:%S%p',
166 170 '%b %d %H:%M',
167 171 '%b %d %I:%M%p',
168 172 '%b %d %Y',
169 173 '%b %d',
170 174 '%H:%M:%S',
171 175 '%I:%M:%S%p',
172 176 '%H:%M',
173 177 '%I:%M%p',
174 178 )
175 179
176 180 extendeddateformats = defaultdateformats + (
177 181 "%Y",
178 182 "%Y-%m",
179 183 "%b",
180 184 "%b %Y",
181 185 )
182 186
183 187 def cachefunc(func):
184 188 '''cache the result of function calls'''
185 189 # XXX doesn't handle keywords args
186 190 cache = {}
187 191 if func.func_code.co_argcount == 1:
188 192 # we gain a small amount of time because
189 193 # we don't need to pack/unpack the list
190 194 def f(arg):
191 195 if arg not in cache:
192 196 cache[arg] = func(arg)
193 197 return cache[arg]
194 198 else:
195 199 def f(*args):
196 200 if args not in cache:
197 201 cache[args] = func(*args)
198 202 return cache[args]
199 203
200 204 return f
201 205
202 206 try:
203 207 collections.deque.remove
204 208 deque = collections.deque
205 209 except AttributeError:
206 210 # python 2.4 lacks deque.remove
207 211 class deque(collections.deque):
208 212 def remove(self, val):
209 213 for i, v in enumerate(self):
210 214 if v == val:
211 215 del self[i]
212 216 break
213 217
214 218 class lrucachedict(object):
215 219 '''cache most recent gets from or sets to this dictionary'''
216 220 def __init__(self, maxsize):
217 221 self._cache = {}
218 222 self._maxsize = maxsize
219 223 self._order = deque()
220 224
221 225 def __getitem__(self, key):
222 226 value = self._cache[key]
223 227 self._order.remove(key)
224 228 self._order.append(key)
225 229 return value
226 230
227 231 def __setitem__(self, key, value):
228 232 if key not in self._cache:
229 233 if len(self._cache) >= self._maxsize:
230 234 del self._cache[self._order.popleft()]
231 235 else:
232 236 self._order.remove(key)
233 237 self._cache[key] = value
234 238 self._order.append(key)
235 239
236 240 def __contains__(self, key):
237 241 return key in self._cache
238 242
239 243 def lrucachefunc(func):
240 244 '''cache most recent results of function calls'''
241 245 cache = {}
242 246 order = deque()
243 247 if func.func_code.co_argcount == 1:
244 248 def f(arg):
245 249 if arg not in cache:
246 250 if len(cache) > 20:
247 251 del cache[order.popleft()]
248 252 cache[arg] = func(arg)
249 253 else:
250 254 order.remove(arg)
251 255 order.append(arg)
252 256 return cache[arg]
253 257 else:
254 258 def f(*args):
255 259 if args not in cache:
256 260 if len(cache) > 20:
257 261 del cache[order.popleft()]
258 262 cache[args] = func(*args)
259 263 else:
260 264 order.remove(args)
261 265 order.append(args)
262 266 return cache[args]
263 267
264 268 return f
265 269
266 270 class propertycache(object):
267 271 def __init__(self, func):
268 272 self.func = func
269 273 self.name = func.__name__
270 274 def __get__(self, obj, type=None):
271 275 result = self.func(obj)
272 276 self.cachevalue(obj, result)
273 277 return result
274 278
275 279 def cachevalue(self, obj, value):
276 280 setattr(obj, self.name, value)
277 281
278 282 def pipefilter(s, cmd):
279 283 '''filter string S through command CMD, returning its output'''
280 284 p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
281 285 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
282 286 pout, perr = p.communicate(s)
283 287 return pout
284 288
285 289 def tempfilter(s, cmd):
286 290 '''filter string S through a pair of temporary files with CMD.
287 291 CMD is used as a template to create the real command to be run,
288 292 with the strings INFILE and OUTFILE replaced by the real names of
289 293 the temporary files generated.'''
290 294 inname, outname = None, None
291 295 try:
292 296 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
293 297 fp = os.fdopen(infd, 'wb')
294 298 fp.write(s)
295 299 fp.close()
296 300 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
297 301 os.close(outfd)
298 302 cmd = cmd.replace('INFILE', inname)
299 303 cmd = cmd.replace('OUTFILE', outname)
300 304 code = os.system(cmd)
301 305 if sys.platform == 'OpenVMS' and code & 1:
302 306 code = 0
303 307 if code:
304 308 raise Abort(_("command '%s' failed: %s") %
305 309 (cmd, explainexit(code)))
306 310 fp = open(outname, 'rb')
307 311 r = fp.read()
308 312 fp.close()
309 313 return r
310 314 finally:
311 315 try:
312 316 if inname:
313 317 os.unlink(inname)
314 318 except OSError:
315 319 pass
316 320 try:
317 321 if outname:
318 322 os.unlink(outname)
319 323 except OSError:
320 324 pass
321 325
322 326 filtertable = {
323 327 'tempfile:': tempfilter,
324 328 'pipe:': pipefilter,
325 329 }
326 330
327 331 def filter(s, cmd):
328 332 "filter a string through a command that transforms its input to its output"
329 333 for name, fn in filtertable.iteritems():
330 334 if cmd.startswith(name):
331 335 return fn(s, cmd[len(name):].lstrip())
332 336 return pipefilter(s, cmd)
333 337
334 338 def binary(s):
335 339 """return true if a string is binary data"""
336 340 return bool(s and '\0' in s)
337 341
338 342 def increasingchunks(source, min=1024, max=65536):
339 343 '''return no less than min bytes per chunk while data remains,
340 344 doubling min after each chunk until it reaches max'''
341 345 def log2(x):
342 346 if not x:
343 347 return 0
344 348 i = 0
345 349 while x:
346 350 x >>= 1
347 351 i += 1
348 352 return i - 1
349 353
350 354 buf = []
351 355 blen = 0
352 356 for chunk in source:
353 357 buf.append(chunk)
354 358 blen += len(chunk)
355 359 if blen >= min:
356 360 if min < max:
357 361 min = min << 1
358 362 nmin = 1 << log2(blen)
359 363 if nmin > min:
360 364 min = nmin
361 365 if min > max:
362 366 min = max
363 367 yield ''.join(buf)
364 368 blen = 0
365 369 buf = []
366 370 if buf:
367 371 yield ''.join(buf)
368 372
369 373 Abort = error.Abort
370 374
371 375 def always(fn):
372 376 return True
373 377
374 378 def never(fn):
375 379 return False
376 380
377 381 def pathto(root, n1, n2):
378 382 '''return the relative path from one place to another.
379 383 root should use os.sep to separate directories
380 384 n1 should use os.sep to separate directories
381 385 n2 should use "/" to separate directories
382 386 returns an os.sep-separated path.
383 387
384 388 If n1 is a relative path, it's assumed it's
385 389 relative to root.
386 390 n2 should always be relative to root.
387 391 '''
388 392 if not n1:
389 393 return localpath(n2)
390 394 if os.path.isabs(n1):
391 395 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
392 396 return os.path.join(root, localpath(n2))
393 397 n2 = '/'.join((pconvert(root), n2))
394 398 a, b = splitpath(n1), n2.split('/')
395 399 a.reverse()
396 400 b.reverse()
397 401 while a and b and a[-1] == b[-1]:
398 402 a.pop()
399 403 b.pop()
400 404 b.reverse()
401 405 return os.sep.join((['..'] * len(a)) + b) or '.'
402 406
403 407 _hgexecutable = None
404 408
405 409 def mainfrozen():
406 410 """return True if we are a frozen executable.
407 411
408 412 The code supports py2exe (most common, Windows only) and tools/freeze
409 413 (portable, not much used).
410 414 """
411 415 return (safehasattr(sys, "frozen") or # new py2exe
412 416 safehasattr(sys, "importers") or # old py2exe
413 417 imp.is_frozen("__main__")) # tools/freeze
414 418
415 419 def hgexecutable():
416 420 """return location of the 'hg' executable.
417 421
418 422 Defaults to $HG or 'hg' in the search path.
419 423 """
420 424 if _hgexecutable is None:
421 425 hg = os.environ.get('HG')
422 426 mainmod = sys.modules['__main__']
423 427 if hg:
424 428 _sethgexecutable(hg)
425 429 elif mainfrozen():
426 430 _sethgexecutable(sys.executable)
427 431 elif os.path.basename(getattr(mainmod, '__file__', '')) == 'hg':
428 432 _sethgexecutable(mainmod.__file__)
429 433 else:
430 434 exe = findexe('hg') or os.path.basename(sys.argv[0])
431 435 _sethgexecutable(exe)
432 436 return _hgexecutable
433 437
434 438 def _sethgexecutable(path):
435 439 """set location of the 'hg' executable"""
436 440 global _hgexecutable
437 441 _hgexecutable = path
438 442
439 443 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None, out=None):
440 444 '''enhanced shell command execution.
441 445 run with environment maybe modified, maybe in different dir.
442 446
443 447 if command fails and onerr is None, return status. if ui object,
444 448 print error message and return status, else raise onerr object as
445 449 exception.
446 450
447 451 if out is specified, it is assumed to be a file-like object that has a
448 452 write() method. stdout and stderr will be redirected to out.'''
449 453 try:
450 454 sys.stdout.flush()
451 455 except Exception:
452 456 pass
453 457 def py2shell(val):
454 458 'convert python object into string that is useful to shell'
455 459 if val is None or val is False:
456 460 return '0'
457 461 if val is True:
458 462 return '1'
459 463 return str(val)
460 464 origcmd = cmd
461 465 cmd = quotecommand(cmd)
462 466 if sys.platform == 'plan9':
463 467 # subprocess kludge to work around issues in half-baked Python
464 468 # ports, notably bichued/python:
465 469 if not cwd is None:
466 470 os.chdir(cwd)
467 471 rc = os.system(cmd)
468 472 else:
469 473 env = dict(os.environ)
470 474 env.update((k, py2shell(v)) for k, v in environ.iteritems())
471 475 env['HG'] = hgexecutable()
472 476 if out is None or out == sys.__stdout__:
473 477 rc = subprocess.call(cmd, shell=True, close_fds=closefds,
474 478 env=env, cwd=cwd)
475 479 else:
476 480 proc = subprocess.Popen(cmd, shell=True, close_fds=closefds,
477 481 env=env, cwd=cwd, stdout=subprocess.PIPE,
478 482 stderr=subprocess.STDOUT)
479 483 for line in proc.stdout:
480 484 out.write(line)
481 485 proc.wait()
482 486 rc = proc.returncode
483 487 if sys.platform == 'OpenVMS' and rc & 1:
484 488 rc = 0
485 489 if rc and onerr:
486 490 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
487 491 explainexit(rc)[0])
488 492 if errprefix:
489 493 errmsg = '%s: %s' % (errprefix, errmsg)
490 494 try:
491 495 onerr.warn(errmsg + '\n')
492 496 except AttributeError:
493 497 raise onerr(errmsg)
494 498 return rc
495 499
496 500 def checksignature(func):
497 501 '''wrap a function with code to check for calling errors'''
498 502 def check(*args, **kwargs):
499 503 try:
500 504 return func(*args, **kwargs)
501 505 except TypeError:
502 506 if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
503 507 raise error.SignatureError
504 508 raise
505 509
506 510 return check
507 511
508 512 def copyfile(src, dest):
509 513 "copy a file, preserving mode and atime/mtime"
510 514 if os.path.lexists(dest):
511 515 unlink(dest)
512 516 if os.path.islink(src):
513 517 os.symlink(os.readlink(src), dest)
514 518 else:
515 519 try:
516 520 shutil.copyfile(src, dest)
517 521 shutil.copymode(src, dest)
518 522 except shutil.Error, inst:
519 523 raise Abort(str(inst))
520 524
521 525 def copyfiles(src, dst, hardlink=None):
522 526 """Copy a directory tree using hardlinks if possible"""
523 527
524 528 if hardlink is None:
525 529 hardlink = (os.stat(src).st_dev ==
526 530 os.stat(os.path.dirname(dst)).st_dev)
527 531
528 532 num = 0
529 533 if os.path.isdir(src):
530 534 os.mkdir(dst)
531 535 for name, kind in osutil.listdir(src):
532 536 srcname = os.path.join(src, name)
533 537 dstname = os.path.join(dst, name)
534 538 hardlink, n = copyfiles(srcname, dstname, hardlink)
535 539 num += n
536 540 else:
537 541 if hardlink:
538 542 try:
539 543 oslink(src, dst)
540 544 except (IOError, OSError):
541 545 hardlink = False
542 546 shutil.copy(src, dst)
543 547 else:
544 548 shutil.copy(src, dst)
545 549 num += 1
546 550
547 551 return hardlink, num
548 552
549 553 _winreservednames = '''con prn aux nul
550 554 com1 com2 com3 com4 com5 com6 com7 com8 com9
551 555 lpt1 lpt2 lpt3 lpt4 lpt5 lpt6 lpt7 lpt8 lpt9'''.split()
552 556 _winreservedchars = ':*?"<>|'
553 557 def checkwinfilename(path):
554 558 '''Check that the base-relative path is a valid filename on Windows.
555 559 Returns None if the path is ok, or a UI string describing the problem.
556 560
557 561 >>> checkwinfilename("just/a/normal/path")
558 562 >>> checkwinfilename("foo/bar/con.xml")
559 563 "filename contains 'con', which is reserved on Windows"
560 564 >>> checkwinfilename("foo/con.xml/bar")
561 565 "filename contains 'con', which is reserved on Windows"
562 566 >>> checkwinfilename("foo/bar/xml.con")
563 567 >>> checkwinfilename("foo/bar/AUX/bla.txt")
564 568 "filename contains 'AUX', which is reserved on Windows"
565 569 >>> checkwinfilename("foo/bar/bla:.txt")
566 570 "filename contains ':', which is reserved on Windows"
567 571 >>> checkwinfilename("foo/bar/b\07la.txt")
568 572 "filename contains '\\\\x07', which is invalid on Windows"
569 573 >>> checkwinfilename("foo/bar/bla ")
570 574 "filename ends with ' ', which is not allowed on Windows"
571 575 >>> checkwinfilename("../bar")
572 576 '''
573 577 for n in path.replace('\\', '/').split('/'):
574 578 if not n:
575 579 continue
576 580 for c in n:
577 581 if c in _winreservedchars:
578 582 return _("filename contains '%s', which is reserved "
579 583 "on Windows") % c
580 584 if ord(c) <= 31:
581 585 return _("filename contains %r, which is invalid "
582 586 "on Windows") % c
583 587 base = n.split('.')[0]
584 588 if base and base.lower() in _winreservednames:
585 589 return _("filename contains '%s', which is reserved "
586 590 "on Windows") % base
587 591 t = n[-1]
588 592 if t in '. ' and n not in '..':
589 593 return _("filename ends with '%s', which is not allowed "
590 594 "on Windows") % t
591 595
592 596 if os.name == 'nt':
593 597 checkosfilename = checkwinfilename
594 598 else:
595 599 checkosfilename = platform.checkosfilename
596 600
597 601 def makelock(info, pathname):
598 602 try:
599 603 return os.symlink(info, pathname)
600 604 except OSError, why:
601 605 if why.errno == errno.EEXIST:
602 606 raise
603 607 except AttributeError: # no symlink in os
604 608 pass
605 609
606 610 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
607 611 os.write(ld, info)
608 612 os.close(ld)
609 613
610 614 def readlock(pathname):
611 615 try:
612 616 return os.readlink(pathname)
613 617 except OSError, why:
614 618 if why.errno not in (errno.EINVAL, errno.ENOSYS):
615 619 raise
616 620 except AttributeError: # no symlink in os
617 621 pass
618 622 fp = posixfile(pathname)
619 623 r = fp.read()
620 624 fp.close()
621 625 return r
622 626
623 627 def fstat(fp):
624 628 '''stat file object that may not have fileno method.'''
625 629 try:
626 630 return os.fstat(fp.fileno())
627 631 except AttributeError:
628 632 return os.stat(fp.name)
629 633
630 634 # File system features
631 635
632 636 def checkcase(path):
633 637 """
634 638 Check whether the given path is on a case-sensitive filesystem
635 639
636 640 Requires a path (like /foo/.hg) ending with a foldable final
637 641 directory component.
638 642 """
639 643 s1 = os.stat(path)
640 644 d, b = os.path.split(path)
641 645 b2 = b.upper()
642 646 if b == b2:
643 647 b2 = b.lower()
644 648 if b == b2:
645 649 return True # no evidence against case sensitivity
646 650 p2 = os.path.join(d, b2)
647 651 try:
648 652 s2 = os.stat(p2)
649 653 if s2 == s1:
650 654 return False
651 655 return True
652 656 except OSError:
653 657 return True
654 658
655 659 try:
656 660 import re2
657 661 _re2 = None
658 662 except ImportError:
659 663 _re2 = False
660 664
661 665 def compilere(pat):
662 666 '''Compile a regular expression, using re2 if possible
663 667
664 668 For best performance, use only re2-compatible regexp features.'''
665 669 global _re2
666 670 if _re2 is None:
667 671 try:
668 672 re2.compile
669 673 _re2 = True
670 674 except ImportError:
671 675 _re2 = False
672 676 if _re2:
673 677 try:
674 678 return re2.compile(pat)
675 679 except re2.error:
676 680 pass
677 681 return re.compile(pat)
678 682
679 683 _fspathcache = {}
680 684 def fspath(name, root):
681 685 '''Get name in the case stored in the filesystem
682 686
683 687 The name should be relative to root, and be normcase-ed for efficiency.
684 688
685 689 Note that this function is unnecessary, and should not be
686 690 called, for case-sensitive filesystems (simply because it's expensive).
687 691
688 692 The root should be normcase-ed, too.
689 693 '''
690 694 def find(p, contents):
691 695 for n in contents:
692 696 if normcase(n) == p:
693 697 return n
694 698 return None
695 699
696 700 seps = os.sep
697 701 if os.altsep:
698 702 seps = seps + os.altsep
699 703 # Protect backslashes. This gets silly very quickly.
700 704 seps.replace('\\','\\\\')
701 705 pattern = re.compile(r'([^%s]+)|([%s]+)' % (seps, seps))
702 706 dir = os.path.normpath(root)
703 707 result = []
704 708 for part, sep in pattern.findall(name):
705 709 if sep:
706 710 result.append(sep)
707 711 continue
708 712
709 713 if dir not in _fspathcache:
710 714 _fspathcache[dir] = os.listdir(dir)
711 715 contents = _fspathcache[dir]
712 716
713 717 found = find(part, contents)
714 718 if not found:
715 719 # retry "once per directory" per "dirstate.walk" which
716 720 # may take place for each patches of "hg qpush", for example
717 721 contents = os.listdir(dir)
718 722 _fspathcache[dir] = contents
719 723 found = find(part, contents)
720 724
721 725 result.append(found or part)
722 726 dir = os.path.join(dir, part)
723 727
724 728 return ''.join(result)
725 729
726 730 def checknlink(testfile):
727 731 '''check whether hardlink count reporting works properly'''
728 732
729 733 # testfile may be open, so we need a separate file for checking to
730 734 # work around issue2543 (or testfile may get lost on Samba shares)
731 735 f1 = testfile + ".hgtmp1"
732 736 if os.path.lexists(f1):
733 737 return False
734 738 try:
735 739 posixfile(f1, 'w').close()
736 740 except IOError:
737 741 return False
738 742
739 743 f2 = testfile + ".hgtmp2"
740 744 fd = None
741 745 try:
742 746 try:
743 747 oslink(f1, f2)
744 748 except OSError:
745 749 return False
746 750
747 751 # nlinks() may behave differently for files on Windows shares if
748 752 # the file is open.
749 753 fd = posixfile(f2)
750 754 return nlinks(f2) > 1
751 755 finally:
752 756 if fd is not None:
753 757 fd.close()
754 758 for f in (f1, f2):
755 759 try:
756 760 os.unlink(f)
757 761 except OSError:
758 762 pass
759 763
760 764 return False
761 765
762 766 def endswithsep(path):
763 767 '''Check path ends with os.sep or os.altsep.'''
764 768 return path.endswith(os.sep) or os.altsep and path.endswith(os.altsep)
765 769
766 770 def splitpath(path):
767 771 '''Split path by os.sep.
768 772 Note that this function does not use os.altsep because this is
769 773 an alternative of simple "xxx.split(os.sep)".
770 774 It is recommended to use os.path.normpath() before using this
771 775 function if need.'''
772 776 return path.split(os.sep)
773 777
774 778 def gui():
775 779 '''Are we running in a GUI?'''
776 780 if sys.platform == 'darwin':
777 781 if 'SSH_CONNECTION' in os.environ:
778 782 # handle SSH access to a box where the user is logged in
779 783 return False
780 784 elif getattr(osutil, 'isgui', None):
781 785 # check if a CoreGraphics session is available
782 786 return osutil.isgui()
783 787 else:
784 788 # pure build; use a safe default
785 789 return True
786 790 else:
787 791 return os.name == "nt" or os.environ.get("DISPLAY")
788 792
789 793 def mktempcopy(name, emptyok=False, createmode=None):
790 794 """Create a temporary file with the same contents from name
791 795
792 796 The permission bits are copied from the original file.
793 797
794 798 If the temporary file is going to be truncated immediately, you
795 799 can use emptyok=True as an optimization.
796 800
797 801 Returns the name of the temporary file.
798 802 """
799 803 d, fn = os.path.split(name)
800 804 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
801 805 os.close(fd)
802 806 # Temporary files are created with mode 0600, which is usually not
803 807 # what we want. If the original file already exists, just copy
804 808 # its mode. Otherwise, manually obey umask.
805 809 copymode(name, temp, createmode)
806 810 if emptyok:
807 811 return temp
808 812 try:
809 813 try:
810 814 ifp = posixfile(name, "rb")
811 815 except IOError, inst:
812 816 if inst.errno == errno.ENOENT:
813 817 return temp
814 818 if not getattr(inst, 'filename', None):
815 819 inst.filename = name
816 820 raise
817 821 ofp = posixfile(temp, "wb")
818 822 for chunk in filechunkiter(ifp):
819 823 ofp.write(chunk)
820 824 ifp.close()
821 825 ofp.close()
822 826 except: # re-raises
823 827 try: os.unlink(temp)
824 828 except OSError: pass
825 829 raise
826 830 return temp
827 831
828 832 class atomictempfile(object):
829 833 '''writable file object that atomically updates a file
830 834
831 835 All writes will go to a temporary copy of the original file. Call
832 836 close() when you are done writing, and atomictempfile will rename
833 837 the temporary copy to the original name, making the changes
834 838 visible. If the object is destroyed without being closed, all your
835 839 writes are discarded.
836 840 '''
837 841 def __init__(self, name, mode='w+b', createmode=None):
838 842 self.__name = name # permanent name
839 843 self._tempname = mktempcopy(name, emptyok=('w' in mode),
840 844 createmode=createmode)
841 845 self._fp = posixfile(self._tempname, mode)
842 846
843 847 # delegated methods
844 848 self.write = self._fp.write
845 849 self.seek = self._fp.seek
846 850 self.tell = self._fp.tell
847 851 self.fileno = self._fp.fileno
848 852
849 853 def close(self):
850 854 if not self._fp.closed:
851 855 self._fp.close()
852 856 rename(self._tempname, localpath(self.__name))
853 857
854 858 def discard(self):
855 859 if not self._fp.closed:
856 860 try:
857 861 os.unlink(self._tempname)
858 862 except OSError:
859 863 pass
860 864 self._fp.close()
861 865
862 866 def __del__(self):
863 867 if safehasattr(self, '_fp'): # constructor actually did something
864 868 self.discard()
865 869
866 870 def makedirs(name, mode=None):
867 871 """recursive directory creation with parent mode inheritance"""
868 872 try:
869 873 os.mkdir(name)
870 874 except OSError, err:
871 875 if err.errno == errno.EEXIST:
872 876 return
873 877 if err.errno != errno.ENOENT or not name:
874 878 raise
875 879 parent = os.path.dirname(os.path.abspath(name))
876 880 if parent == name:
877 881 raise
878 882 makedirs(parent, mode)
879 883 os.mkdir(name)
880 884 if mode is not None:
881 885 os.chmod(name, mode)
882 886
883 887 def ensuredirs(name, mode=None):
884 888 """race-safe recursive directory creation"""
885 889 if os.path.isdir(name):
886 890 return
887 891 parent = os.path.dirname(os.path.abspath(name))
888 892 if parent != name:
889 893 ensuredirs(parent, mode)
890 894 try:
891 895 os.mkdir(name)
892 896 except OSError, err:
893 897 if err.errno == errno.EEXIST and os.path.isdir(name):
894 898 # someone else seems to have won a directory creation race
895 899 return
896 900 raise
897 901 if mode is not None:
898 902 os.chmod(name, mode)
899 903
900 904 def readfile(path):
901 905 fp = open(path, 'rb')
902 906 try:
903 907 return fp.read()
904 908 finally:
905 909 fp.close()
906 910
907 911 def writefile(path, text):
908 912 fp = open(path, 'wb')
909 913 try:
910 914 fp.write(text)
911 915 finally:
912 916 fp.close()
913 917
914 918 def appendfile(path, text):
915 919 fp = open(path, 'ab')
916 920 try:
917 921 fp.write(text)
918 922 finally:
919 923 fp.close()
920 924
921 925 class chunkbuffer(object):
922 926 """Allow arbitrary sized chunks of data to be efficiently read from an
923 927 iterator over chunks of arbitrary size."""
924 928
925 929 def __init__(self, in_iter):
926 930 """in_iter is the iterator that's iterating over the input chunks.
927 931 targetsize is how big a buffer to try to maintain."""
928 932 def splitbig(chunks):
929 933 for chunk in chunks:
930 934 if len(chunk) > 2**20:
931 935 pos = 0
932 936 while pos < len(chunk):
933 937 end = pos + 2 ** 18
934 938 yield chunk[pos:end]
935 939 pos = end
936 940 else:
937 941 yield chunk
938 942 self.iter = splitbig(in_iter)
939 943 self._queue = deque()
940 944
941 945 def read(self, l):
942 946 """Read L bytes of data from the iterator of chunks of data.
943 947 Returns less than L bytes if the iterator runs dry."""
944 948 left = l
945 949 buf = []
946 950 queue = self._queue
947 951 while left > 0:
948 952 # refill the queue
949 953 if not queue:
950 954 target = 2**18
951 955 for chunk in self.iter:
952 956 queue.append(chunk)
953 957 target -= len(chunk)
954 958 if target <= 0:
955 959 break
956 960 if not queue:
957 961 break
958 962
959 963 chunk = queue.popleft()
960 964 left -= len(chunk)
961 965 if left < 0:
962 966 queue.appendleft(chunk[left:])
963 967 buf.append(chunk[:left])
964 968 else:
965 969 buf.append(chunk)
966 970
967 971 return ''.join(buf)
968 972
969 973 def filechunkiter(f, size=65536, limit=None):
970 974 """Create a generator that produces the data in the file size
971 975 (default 65536) bytes at a time, up to optional limit (default is
972 976 to read all data). Chunks may be less than size bytes if the
973 977 chunk is the last chunk in the file, or the file is a socket or
974 978 some other type of file that sometimes reads less data than is
975 979 requested."""
976 980 assert size >= 0
977 981 assert limit is None or limit >= 0
978 982 while True:
979 983 if limit is None:
980 984 nbytes = size
981 985 else:
982 986 nbytes = min(limit, size)
983 987 s = nbytes and f.read(nbytes)
984 988 if not s:
985 989 break
986 990 if limit:
987 991 limit -= len(s)
988 992 yield s
989 993
990 994 def makedate():
991 995 ct = time.time()
992 996 if ct < 0:
993 997 hint = _("check your clock")
994 998 raise Abort(_("negative timestamp: %d") % ct, hint=hint)
995 999 delta = (datetime.datetime.utcfromtimestamp(ct) -
996 1000 datetime.datetime.fromtimestamp(ct))
997 1001 tz = delta.days * 86400 + delta.seconds
998 1002 return ct, tz
999 1003
1000 1004 def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
1001 1005 """represent a (unixtime, offset) tuple as a localized time.
1002 1006 unixtime is seconds since the epoch, and offset is the time zone's
1003 1007 number of seconds away from UTC. if timezone is false, do not
1004 1008 append time zone to string."""
1005 1009 t, tz = date or makedate()
1006 1010 if t < 0:
1007 1011 t = 0 # time.gmtime(lt) fails on Windows for lt < -43200
1008 1012 tz = 0
1009 1013 if "%1" in format or "%2" in format:
1010 1014 sign = (tz > 0) and "-" or "+"
1011 1015 minutes = abs(tz) // 60
1012 1016 format = format.replace("%1", "%c%02d" % (sign, minutes // 60))
1013 1017 format = format.replace("%2", "%02d" % (minutes % 60))
1014 1018 try:
1015 1019 t = time.gmtime(float(t) - tz)
1016 1020 except ValueError:
1017 1021 # time was out of range
1018 1022 t = time.gmtime(sys.maxint)
1019 1023 s = time.strftime(format, t)
1020 1024 return s
1021 1025
1022 1026 def shortdate(date=None):
1023 1027 """turn (timestamp, tzoff) tuple into iso 8631 date."""
1024 1028 return datestr(date, format='%Y-%m-%d')
1025 1029
1026 1030 def strdate(string, format, defaults=[]):
1027 1031 """parse a localized time string and return a (unixtime, offset) tuple.
1028 1032 if the string cannot be parsed, ValueError is raised."""
1029 1033 def timezone(string):
1030 1034 tz = string.split()[-1]
1031 1035 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
1032 1036 sign = (tz[0] == "+") and 1 or -1
1033 1037 hours = int(tz[1:3])
1034 1038 minutes = int(tz[3:5])
1035 1039 return -sign * (hours * 60 + minutes) * 60
1036 1040 if tz == "GMT" or tz == "UTC":
1037 1041 return 0
1038 1042 return None
1039 1043
1040 1044 # NOTE: unixtime = localunixtime + offset
1041 1045 offset, date = timezone(string), string
1042 1046 if offset is not None:
1043 1047 date = " ".join(string.split()[:-1])
1044 1048
1045 1049 # add missing elements from defaults
1046 1050 usenow = False # default to using biased defaults
1047 1051 for part in ("S", "M", "HI", "d", "mb", "yY"): # decreasing specificity
1048 1052 found = [True for p in part if ("%"+p) in format]
1049 1053 if not found:
1050 1054 date += "@" + defaults[part][usenow]
1051 1055 format += "@%" + part[0]
1052 1056 else:
1053 1057 # We've found a specific time element, less specific time
1054 1058 # elements are relative to today
1055 1059 usenow = True
1056 1060
1057 1061 timetuple = time.strptime(date, format)
1058 1062 localunixtime = int(calendar.timegm(timetuple))
1059 1063 if offset is None:
1060 1064 # local timezone
1061 1065 unixtime = int(time.mktime(timetuple))
1062 1066 offset = unixtime - localunixtime
1063 1067 else:
1064 1068 unixtime = localunixtime + offset
1065 1069 return unixtime, offset
1066 1070
1067 1071 def parsedate(date, formats=None, bias={}):
1068 1072 """parse a localized date/time and return a (unixtime, offset) tuple.
1069 1073
1070 1074 The date may be a "unixtime offset" string or in one of the specified
1071 1075 formats. If the date already is a (unixtime, offset) tuple, it is returned.
1072 1076
1073 1077 >>> parsedate(' today ') == parsedate(\
1074 1078 datetime.date.today().strftime('%b %d'))
1075 1079 True
1076 1080 >>> parsedate( 'yesterday ') == parsedate((datetime.date.today() -\
1077 1081 datetime.timedelta(days=1)\
1078 1082 ).strftime('%b %d'))
1079 1083 True
1080 1084 >>> now, tz = makedate()
1081 1085 >>> strnow, strtz = parsedate('now')
1082 1086 >>> (strnow - now) < 1
1083 1087 True
1084 1088 >>> tz == strtz
1085 1089 True
1086 1090 """
1087 1091 if not date:
1088 1092 return 0, 0
1089 1093 if isinstance(date, tuple) and len(date) == 2:
1090 1094 return date
1091 1095 if not formats:
1092 1096 formats = defaultdateformats
1093 1097 date = date.strip()
1094 1098
1095 1099 if date == _('now'):
1096 1100 return makedate()
1097 1101 if date == _('today'):
1098 1102 date = datetime.date.today().strftime('%b %d')
1099 1103 elif date == _('yesterday'):
1100 1104 date = (datetime.date.today() -
1101 1105 datetime.timedelta(days=1)).strftime('%b %d')
1102 1106
1103 1107 try:
1104 1108 when, offset = map(int, date.split(' '))
1105 1109 except ValueError:
1106 1110 # fill out defaults
1107 1111 now = makedate()
1108 1112 defaults = {}
1109 1113 for part in ("d", "mb", "yY", "HI", "M", "S"):
1110 1114 # this piece is for rounding the specific end of unknowns
1111 1115 b = bias.get(part)
1112 1116 if b is None:
1113 1117 if part[0] in "HMS":
1114 1118 b = "00"
1115 1119 else:
1116 1120 b = "0"
1117 1121
1118 1122 # this piece is for matching the generic end to today's date
1119 1123 n = datestr(now, "%" + part[0])
1120 1124
1121 1125 defaults[part] = (b, n)
1122 1126
1123 1127 for format in formats:
1124 1128 try:
1125 1129 when, offset = strdate(date, format, defaults)
1126 1130 except (ValueError, OverflowError):
1127 1131 pass
1128 1132 else:
1129 1133 break
1130 1134 else:
1131 1135 raise Abort(_('invalid date: %r') % date)
1132 1136 # validate explicit (probably user-specified) date and
1133 1137 # time zone offset. values must fit in signed 32 bits for
1134 1138 # current 32-bit linux runtimes. timezones go from UTC-12
1135 1139 # to UTC+14
1136 1140 if abs(when) > 0x7fffffff:
1137 1141 raise Abort(_('date exceeds 32 bits: %d') % when)
1138 1142 if when < 0:
1139 1143 raise Abort(_('negative date value: %d') % when)
1140 1144 if offset < -50400 or offset > 43200:
1141 1145 raise Abort(_('impossible time zone offset: %d') % offset)
1142 1146 return when, offset
1143 1147
1144 1148 def matchdate(date):
1145 1149 """Return a function that matches a given date match specifier
1146 1150
1147 1151 Formats include:
1148 1152
1149 1153 '{date}' match a given date to the accuracy provided
1150 1154
1151 1155 '<{date}' on or before a given date
1152 1156
1153 1157 '>{date}' on or after a given date
1154 1158
1155 1159 >>> p1 = parsedate("10:29:59")
1156 1160 >>> p2 = parsedate("10:30:00")
1157 1161 >>> p3 = parsedate("10:30:59")
1158 1162 >>> p4 = parsedate("10:31:00")
1159 1163 >>> p5 = parsedate("Sep 15 10:30:00 1999")
1160 1164 >>> f = matchdate("10:30")
1161 1165 >>> f(p1[0])
1162 1166 False
1163 1167 >>> f(p2[0])
1164 1168 True
1165 1169 >>> f(p3[0])
1166 1170 True
1167 1171 >>> f(p4[0])
1168 1172 False
1169 1173 >>> f(p5[0])
1170 1174 False
1171 1175 """
1172 1176
1173 1177 def lower(date):
1174 1178 d = dict(mb="1", d="1")
1175 1179 return parsedate(date, extendeddateformats, d)[0]
1176 1180
1177 1181 def upper(date):
1178 1182 d = dict(mb="12", HI="23", M="59", S="59")
1179 1183 for days in ("31", "30", "29"):
1180 1184 try:
1181 1185 d["d"] = days
1182 1186 return parsedate(date, extendeddateformats, d)[0]
1183 1187 except Abort:
1184 1188 pass
1185 1189 d["d"] = "28"
1186 1190 return parsedate(date, extendeddateformats, d)[0]
1187 1191
1188 1192 date = date.strip()
1189 1193
1190 1194 if not date:
1191 1195 raise Abort(_("dates cannot consist entirely of whitespace"))
1192 1196 elif date[0] == "<":
1193 1197 if not date[1:]:
1194 1198 raise Abort(_("invalid day spec, use '<DATE'"))
1195 1199 when = upper(date[1:])
1196 1200 return lambda x: x <= when
1197 1201 elif date[0] == ">":
1198 1202 if not date[1:]:
1199 1203 raise Abort(_("invalid day spec, use '>DATE'"))
1200 1204 when = lower(date[1:])
1201 1205 return lambda x: x >= when
1202 1206 elif date[0] == "-":
1203 1207 try:
1204 1208 days = int(date[1:])
1205 1209 except ValueError:
1206 1210 raise Abort(_("invalid day spec: %s") % date[1:])
1207 1211 if days < 0:
1208 1212 raise Abort(_("%s must be nonnegative (see 'hg help dates')")
1209 1213 % date[1:])
1210 1214 when = makedate()[0] - days * 3600 * 24
1211 1215 return lambda x: x >= when
1212 1216 elif " to " in date:
1213 1217 a, b = date.split(" to ")
1214 1218 start, stop = lower(a), upper(b)
1215 1219 return lambda x: x >= start and x <= stop
1216 1220 else:
1217 1221 start, stop = lower(date), upper(date)
1218 1222 return lambda x: x >= start and x <= stop
1219 1223
1220 1224 def shortuser(user):
1221 1225 """Return a short representation of a user name or email address."""
1222 1226 f = user.find('@')
1223 1227 if f >= 0:
1224 1228 user = user[:f]
1225 1229 f = user.find('<')
1226 1230 if f >= 0:
1227 1231 user = user[f + 1:]
1228 1232 f = user.find(' ')
1229 1233 if f >= 0:
1230 1234 user = user[:f]
1231 1235 f = user.find('.')
1232 1236 if f >= 0:
1233 1237 user = user[:f]
1234 1238 return user
1235 1239
1236 1240 def emailuser(user):
1237 1241 """Return the user portion of an email address."""
1238 1242 f = user.find('@')
1239 1243 if f >= 0:
1240 1244 user = user[:f]
1241 1245 f = user.find('<')
1242 1246 if f >= 0:
1243 1247 user = user[f + 1:]
1244 1248 return user
1245 1249
1246 1250 def email(author):
1247 1251 '''get email of author.'''
1248 1252 r = author.find('>')
1249 1253 if r == -1:
1250 1254 r = None
1251 1255 return author[author.find('<') + 1:r]
1252 1256
1253 1257 def _ellipsis(text, maxlength):
1254 1258 if len(text) <= maxlength:
1255 1259 return text, False
1256 1260 else:
1257 1261 return "%s..." % (text[:maxlength - 3]), True
1258 1262
1259 1263 def ellipsis(text, maxlength=400):
1260 1264 """Trim string to at most maxlength (default: 400) characters."""
1261 1265 try:
1262 1266 # use unicode not to split at intermediate multi-byte sequence
1263 1267 utext, truncated = _ellipsis(text.decode(encoding.encoding),
1264 1268 maxlength)
1265 1269 if not truncated:
1266 1270 return text
1267 1271 return utext.encode(encoding.encoding)
1268 1272 except (UnicodeDecodeError, UnicodeEncodeError):
1269 1273 return _ellipsis(text, maxlength)[0]
1270 1274
1271 1275 def unitcountfn(*unittable):
1272 1276 '''return a function that renders a readable count of some quantity'''
1273 1277
1274 1278 def go(count):
1275 1279 for multiplier, divisor, format in unittable:
1276 1280 if count >= divisor * multiplier:
1277 1281 return format % (count / float(divisor))
1278 1282 return unittable[-1][2] % count
1279 1283
1280 1284 return go
1281 1285
1282 1286 bytecount = unitcountfn(
1283 1287 (100, 1 << 30, _('%.0f GB')),
1284 1288 (10, 1 << 30, _('%.1f GB')),
1285 1289 (1, 1 << 30, _('%.2f GB')),
1286 1290 (100, 1 << 20, _('%.0f MB')),
1287 1291 (10, 1 << 20, _('%.1f MB')),
1288 1292 (1, 1 << 20, _('%.2f MB')),
1289 1293 (100, 1 << 10, _('%.0f KB')),
1290 1294 (10, 1 << 10, _('%.1f KB')),
1291 1295 (1, 1 << 10, _('%.2f KB')),
1292 1296 (1, 1, _('%.0f bytes')),
1293 1297 )
1294 1298
1295 1299 def uirepr(s):
1296 1300 # Avoid double backslash in Windows path repr()
1297 1301 return repr(s).replace('\\\\', '\\')
1298 1302
1299 1303 # delay import of textwrap
1300 1304 def MBTextWrapper(**kwargs):
1301 1305 class tw(textwrap.TextWrapper):
1302 1306 """
1303 1307 Extend TextWrapper for width-awareness.
1304 1308
1305 1309 Neither number of 'bytes' in any encoding nor 'characters' is
1306 1310 appropriate to calculate terminal columns for specified string.
1307 1311
1308 1312 Original TextWrapper implementation uses built-in 'len()' directly,
1309 1313 so overriding is needed to use width information of each characters.
1310 1314
1311 1315 In addition, characters classified into 'ambiguous' width are
1312 1316 treated as wide in East Asian area, but as narrow in other.
1313 1317
1314 1318 This requires use decision to determine width of such characters.
1315 1319 """
1316 1320 def __init__(self, **kwargs):
1317 1321 textwrap.TextWrapper.__init__(self, **kwargs)
1318 1322
1319 1323 # for compatibility between 2.4 and 2.6
1320 1324 if getattr(self, 'drop_whitespace', None) is None:
1321 1325 self.drop_whitespace = kwargs.get('drop_whitespace', True)
1322 1326
1323 1327 def _cutdown(self, ucstr, space_left):
1324 1328 l = 0
1325 1329 colwidth = encoding.ucolwidth
1326 1330 for i in xrange(len(ucstr)):
1327 1331 l += colwidth(ucstr[i])
1328 1332 if space_left < l:
1329 1333 return (ucstr[:i], ucstr[i:])
1330 1334 return ucstr, ''
1331 1335
1332 1336 # overriding of base class
1333 1337 def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width):
1334 1338 space_left = max(width - cur_len, 1)
1335 1339
1336 1340 if self.break_long_words:
1337 1341 cut, res = self._cutdown(reversed_chunks[-1], space_left)
1338 1342 cur_line.append(cut)
1339 1343 reversed_chunks[-1] = res
1340 1344 elif not cur_line:
1341 1345 cur_line.append(reversed_chunks.pop())
1342 1346
1343 1347 # this overriding code is imported from TextWrapper of python 2.6
1344 1348 # to calculate columns of string by 'encoding.ucolwidth()'
1345 1349 def _wrap_chunks(self, chunks):
1346 1350 colwidth = encoding.ucolwidth
1347 1351
1348 1352 lines = []
1349 1353 if self.width <= 0:
1350 1354 raise ValueError("invalid width %r (must be > 0)" % self.width)
1351 1355
1352 1356 # Arrange in reverse order so items can be efficiently popped
1353 1357 # from a stack of chucks.
1354 1358 chunks.reverse()
1355 1359
1356 1360 while chunks:
1357 1361
1358 1362 # Start the list of chunks that will make up the current line.
1359 1363 # cur_len is just the length of all the chunks in cur_line.
1360 1364 cur_line = []
1361 1365 cur_len = 0
1362 1366
1363 1367 # Figure out which static string will prefix this line.
1364 1368 if lines:
1365 1369 indent = self.subsequent_indent
1366 1370 else:
1367 1371 indent = self.initial_indent
1368 1372
1369 1373 # Maximum width for this line.
1370 1374 width = self.width - len(indent)
1371 1375
1372 1376 # First chunk on line is whitespace -- drop it, unless this
1373 1377 # is the very beginning of the text (i.e. no lines started yet).
1374 1378 if self.drop_whitespace and chunks[-1].strip() == '' and lines:
1375 1379 del chunks[-1]
1376 1380
1377 1381 while chunks:
1378 1382 l = colwidth(chunks[-1])
1379 1383
1380 1384 # Can at least squeeze this chunk onto the current line.
1381 1385 if cur_len + l <= width:
1382 1386 cur_line.append(chunks.pop())
1383 1387 cur_len += l
1384 1388
1385 1389 # Nope, this line is full.
1386 1390 else:
1387 1391 break
1388 1392
1389 1393 # The current line is full, and the next chunk is too big to
1390 1394 # fit on *any* line (not just this one).
1391 1395 if chunks and colwidth(chunks[-1]) > width:
1392 1396 self._handle_long_word(chunks, cur_line, cur_len, width)
1393 1397
1394 1398 # If the last chunk on this line is all whitespace, drop it.
1395 1399 if (self.drop_whitespace and
1396 1400 cur_line and cur_line[-1].strip() == ''):
1397 1401 del cur_line[-1]
1398 1402
1399 1403 # Convert current line back to a string and store it in list
1400 1404 # of all lines (return value).
1401 1405 if cur_line:
1402 1406 lines.append(indent + ''.join(cur_line))
1403 1407
1404 1408 return lines
1405 1409
1406 1410 global MBTextWrapper
1407 1411 MBTextWrapper = tw
1408 1412 return tw(**kwargs)
1409 1413
1410 1414 def wrap(line, width, initindent='', hangindent=''):
1411 1415 maxindent = max(len(hangindent), len(initindent))
1412 1416 if width <= maxindent:
1413 1417 # adjust for weird terminal size
1414 1418 width = max(78, maxindent + 1)
1415 1419 line = line.decode(encoding.encoding, encoding.encodingmode)
1416 1420 initindent = initindent.decode(encoding.encoding, encoding.encodingmode)
1417 1421 hangindent = hangindent.decode(encoding.encoding, encoding.encodingmode)
1418 1422 wrapper = MBTextWrapper(width=width,
1419 1423 initial_indent=initindent,
1420 1424 subsequent_indent=hangindent)
1421 1425 return wrapper.fill(line).encode(encoding.encoding)
1422 1426
1423 1427 def iterlines(iterator):
1424 1428 for chunk in iterator:
1425 1429 for line in chunk.splitlines():
1426 1430 yield line
1427 1431
1428 1432 def expandpath(path):
1429 1433 return os.path.expanduser(os.path.expandvars(path))
1430 1434
1431 1435 def hgcmd():
1432 1436 """Return the command used to execute current hg
1433 1437
1434 1438 This is different from hgexecutable() because on Windows we want
1435 1439 to avoid things opening new shell windows like batch files, so we
1436 1440 get either the python call or current executable.
1437 1441 """
1438 1442 if mainfrozen():
1439 1443 return [sys.executable]
1440 1444 return gethgcmd()
1441 1445
1442 1446 def rundetached(args, condfn):
1443 1447 """Execute the argument list in a detached process.
1444 1448
1445 1449 condfn is a callable which is called repeatedly and should return
1446 1450 True once the child process is known to have started successfully.
1447 1451 At this point, the child process PID is returned. If the child
1448 1452 process fails to start or finishes before condfn() evaluates to
1449 1453 True, return -1.
1450 1454 """
1451 1455 # Windows case is easier because the child process is either
1452 1456 # successfully starting and validating the condition or exiting
1453 1457 # on failure. We just poll on its PID. On Unix, if the child
1454 1458 # process fails to start, it will be left in a zombie state until
1455 1459 # the parent wait on it, which we cannot do since we expect a long
1456 1460 # running process on success. Instead we listen for SIGCHLD telling
1457 1461 # us our child process terminated.
1458 1462 terminated = set()
1459 1463 def handler(signum, frame):
1460 1464 terminated.add(os.wait())
1461 1465 prevhandler = None
1462 1466 SIGCHLD = getattr(signal, 'SIGCHLD', None)
1463 1467 if SIGCHLD is not None:
1464 1468 prevhandler = signal.signal(SIGCHLD, handler)
1465 1469 try:
1466 1470 pid = spawndetached(args)
1467 1471 while not condfn():
1468 1472 if ((pid in terminated or not testpid(pid))
1469 1473 and not condfn()):
1470 1474 return -1
1471 1475 time.sleep(0.1)
1472 1476 return pid
1473 1477 finally:
1474 1478 if prevhandler is not None:
1475 1479 signal.signal(signal.SIGCHLD, prevhandler)
1476 1480
1477 1481 try:
1478 1482 any, all = any, all
1479 1483 except NameError:
1480 1484 def any(iterable):
1481 1485 for i in iterable:
1482 1486 if i:
1483 1487 return True
1484 1488 return False
1485 1489
1486 1490 def all(iterable):
1487 1491 for i in iterable:
1488 1492 if not i:
1489 1493 return False
1490 1494 return True
1491 1495
1492 1496 def interpolate(prefix, mapping, s, fn=None, escape_prefix=False):
1493 1497 """Return the result of interpolating items in the mapping into string s.
1494 1498
1495 1499 prefix is a single character string, or a two character string with
1496 1500 a backslash as the first character if the prefix needs to be escaped in
1497 1501 a regular expression.
1498 1502
1499 1503 fn is an optional function that will be applied to the replacement text
1500 1504 just before replacement.
1501 1505
1502 1506 escape_prefix is an optional flag that allows using doubled prefix for
1503 1507 its escaping.
1504 1508 """
1505 1509 fn = fn or (lambda s: s)
1506 1510 patterns = '|'.join(mapping.keys())
1507 1511 if escape_prefix:
1508 1512 patterns += '|' + prefix
1509 1513 if len(prefix) > 1:
1510 1514 prefix_char = prefix[1:]
1511 1515 else:
1512 1516 prefix_char = prefix
1513 1517 mapping[prefix_char] = prefix_char
1514 1518 r = re.compile(r'%s(%s)' % (prefix, patterns))
1515 1519 return r.sub(lambda x: fn(mapping[x.group()[1:]]), s)
1516 1520
1517 1521 def getport(port):
1518 1522 """Return the port for a given network service.
1519 1523
1520 1524 If port is an integer, it's returned as is. If it's a string, it's
1521 1525 looked up using socket.getservbyname(). If there's no matching
1522 1526 service, util.Abort is raised.
1523 1527 """
1524 1528 try:
1525 1529 return int(port)
1526 1530 except ValueError:
1527 1531 pass
1528 1532
1529 1533 try:
1530 1534 return socket.getservbyname(port)
1531 1535 except socket.error:
1532 1536 raise Abort(_("no port number associated with service '%s'") % port)
1533 1537
1534 1538 _booleans = {'1': True, 'yes': True, 'true': True, 'on': True, 'always': True,
1535 1539 '0': False, 'no': False, 'false': False, 'off': False,
1536 1540 'never': False}
1537 1541
1538 1542 def parsebool(s):
1539 1543 """Parse s into a boolean.
1540 1544
1541 1545 If s is not a valid boolean, returns None.
1542 1546 """
1543 1547 return _booleans.get(s.lower(), None)
1544 1548
1545 1549 _hexdig = '0123456789ABCDEFabcdef'
1546 1550 _hextochr = dict((a + b, chr(int(a + b, 16)))
1547 1551 for a in _hexdig for b in _hexdig)
1548 1552
1549 1553 def _urlunquote(s):
1550 1554 """Decode HTTP/HTML % encoding.
1551 1555
1552 1556 >>> _urlunquote('abc%20def')
1553 1557 'abc def'
1554 1558 """
1555 1559 res = s.split('%')
1556 1560 # fastpath
1557 1561 if len(res) == 1:
1558 1562 return s
1559 1563 s = res[0]
1560 1564 for item in res[1:]:
1561 1565 try:
1562 1566 s += _hextochr[item[:2]] + item[2:]
1563 1567 except KeyError:
1564 1568 s += '%' + item
1565 1569 except UnicodeDecodeError:
1566 1570 s += unichr(int(item[:2], 16)) + item[2:]
1567 1571 return s
1568 1572
1569 1573 class url(object):
1570 1574 r"""Reliable URL parser.
1571 1575
1572 1576 This parses URLs and provides attributes for the following
1573 1577 components:
1574 1578
1575 1579 <scheme>://<user>:<passwd>@<host>:<port>/<path>?<query>#<fragment>
1576 1580
1577 1581 Missing components are set to None. The only exception is
1578 1582 fragment, which is set to '' if present but empty.
1579 1583
1580 1584 If parsefragment is False, fragment is included in query. If
1581 1585 parsequery is False, query is included in path. If both are
1582 1586 False, both fragment and query are included in path.
1583 1587
1584 1588 See http://www.ietf.org/rfc/rfc2396.txt for more information.
1585 1589
1586 1590 Note that for backward compatibility reasons, bundle URLs do not
1587 1591 take host names. That means 'bundle://../' has a path of '../'.
1588 1592
1589 1593 Examples:
1590 1594
1591 1595 >>> url('http://www.ietf.org/rfc/rfc2396.txt')
1592 1596 <url scheme: 'http', host: 'www.ietf.org', path: 'rfc/rfc2396.txt'>
1593 1597 >>> url('ssh://[::1]:2200//home/joe/repo')
1594 1598 <url scheme: 'ssh', host: '[::1]', port: '2200', path: '/home/joe/repo'>
1595 1599 >>> url('file:///home/joe/repo')
1596 1600 <url scheme: 'file', path: '/home/joe/repo'>
1597 1601 >>> url('file:///c:/temp/foo/')
1598 1602 <url scheme: 'file', path: 'c:/temp/foo/'>
1599 1603 >>> url('bundle:foo')
1600 1604 <url scheme: 'bundle', path: 'foo'>
1601 1605 >>> url('bundle://../foo')
1602 1606 <url scheme: 'bundle', path: '../foo'>
1603 1607 >>> url(r'c:\foo\bar')
1604 1608 <url path: 'c:\\foo\\bar'>
1605 1609 >>> url(r'\\blah\blah\blah')
1606 1610 <url path: '\\\\blah\\blah\\blah'>
1607 1611 >>> url(r'\\blah\blah\blah#baz')
1608 1612 <url path: '\\\\blah\\blah\\blah', fragment: 'baz'>
1609 1613
1610 1614 Authentication credentials:
1611 1615
1612 1616 >>> url('ssh://joe:xyz@x/repo')
1613 1617 <url scheme: 'ssh', user: 'joe', passwd: 'xyz', host: 'x', path: 'repo'>
1614 1618 >>> url('ssh://joe@x/repo')
1615 1619 <url scheme: 'ssh', user: 'joe', host: 'x', path: 'repo'>
1616 1620
1617 1621 Query strings and fragments:
1618 1622
1619 1623 >>> url('http://host/a?b#c')
1620 1624 <url scheme: 'http', host: 'host', path: 'a', query: 'b', fragment: 'c'>
1621 1625 >>> url('http://host/a?b#c', parsequery=False, parsefragment=False)
1622 1626 <url scheme: 'http', host: 'host', path: 'a?b#c'>
1623 1627 """
1624 1628
1625 1629 _safechars = "!~*'()+"
1626 1630 _safepchars = "/!~*'()+:"
1627 1631 _matchscheme = re.compile(r'^[a-zA-Z0-9+.\-]+:').match
1628 1632
1629 1633 def __init__(self, path, parsequery=True, parsefragment=True):
1630 1634 # We slowly chomp away at path until we have only the path left
1631 1635 self.scheme = self.user = self.passwd = self.host = None
1632 1636 self.port = self.path = self.query = self.fragment = None
1633 1637 self._localpath = True
1634 1638 self._hostport = ''
1635 1639 self._origpath = path
1636 1640
1637 1641 if parsefragment and '#' in path:
1638 1642 path, self.fragment = path.split('#', 1)
1639 1643 if not path:
1640 1644 path = None
1641 1645
1642 1646 # special case for Windows drive letters and UNC paths
1643 1647 if hasdriveletter(path) or path.startswith(r'\\'):
1644 1648 self.path = path
1645 1649 return
1646 1650
1647 1651 # For compatibility reasons, we can't handle bundle paths as
1648 1652 # normal URLS
1649 1653 if path.startswith('bundle:'):
1650 1654 self.scheme = 'bundle'
1651 1655 path = path[7:]
1652 1656 if path.startswith('//'):
1653 1657 path = path[2:]
1654 1658 self.path = path
1655 1659 return
1656 1660
1657 1661 if self._matchscheme(path):
1658 1662 parts = path.split(':', 1)
1659 1663 if parts[0]:
1660 1664 self.scheme, path = parts
1661 1665 self._localpath = False
1662 1666
1663 1667 if not path:
1664 1668 path = None
1665 1669 if self._localpath:
1666 1670 self.path = ''
1667 1671 return
1668 1672 else:
1669 1673 if self._localpath:
1670 1674 self.path = path
1671 1675 return
1672 1676
1673 1677 if parsequery and '?' in path:
1674 1678 path, self.query = path.split('?', 1)
1675 1679 if not path:
1676 1680 path = None
1677 1681 if not self.query:
1678 1682 self.query = None
1679 1683
1680 1684 # // is required to specify a host/authority
1681 1685 if path and path.startswith('//'):
1682 1686 parts = path[2:].split('/', 1)
1683 1687 if len(parts) > 1:
1684 1688 self.host, path = parts
1685 1689 path = path
1686 1690 else:
1687 1691 self.host = parts[0]
1688 1692 path = None
1689 1693 if not self.host:
1690 1694 self.host = None
1691 1695 # path of file:///d is /d
1692 1696 # path of file:///d:/ is d:/, not /d:/
1693 1697 if path and not hasdriveletter(path):
1694 1698 path = '/' + path
1695 1699
1696 1700 if self.host and '@' in self.host:
1697 1701 self.user, self.host = self.host.rsplit('@', 1)
1698 1702 if ':' in self.user:
1699 1703 self.user, self.passwd = self.user.split(':', 1)
1700 1704 if not self.host:
1701 1705 self.host = None
1702 1706
1703 1707 # Don't split on colons in IPv6 addresses without ports
1704 1708 if (self.host and ':' in self.host and
1705 1709 not (self.host.startswith('[') and self.host.endswith(']'))):
1706 1710 self._hostport = self.host
1707 1711 self.host, self.port = self.host.rsplit(':', 1)
1708 1712 if not self.host:
1709 1713 self.host = None
1710 1714
1711 1715 if (self.host and self.scheme == 'file' and
1712 1716 self.host not in ('localhost', '127.0.0.1', '[::1]')):
1713 1717 raise Abort(_('file:// URLs can only refer to localhost'))
1714 1718
1715 1719 self.path = path
1716 1720
1717 1721 # leave the query string escaped
1718 1722 for a in ('user', 'passwd', 'host', 'port',
1719 1723 'path', 'fragment'):
1720 1724 v = getattr(self, a)
1721 1725 if v is not None:
1722 1726 setattr(self, a, _urlunquote(v))
1723 1727
1724 1728 def __repr__(self):
1725 1729 attrs = []
1726 1730 for a in ('scheme', 'user', 'passwd', 'host', 'port', 'path',
1727 1731 'query', 'fragment'):
1728 1732 v = getattr(self, a)
1729 1733 if v is not None:
1730 1734 attrs.append('%s: %r' % (a, v))
1731 1735 return '<url %s>' % ', '.join(attrs)
1732 1736
1733 1737 def __str__(self):
1734 1738 r"""Join the URL's components back into a URL string.
1735 1739
1736 1740 Examples:
1737 1741
1738 1742 >>> str(url('http://user:pw@host:80/c:/bob?fo:oo#ba:ar'))
1739 1743 'http://user:pw@host:80/c:/bob?fo:oo#ba:ar'
1740 1744 >>> str(url('http://user:pw@host:80/?foo=bar&baz=42'))
1741 1745 'http://user:pw@host:80/?foo=bar&baz=42'
1742 1746 >>> str(url('http://user:pw@host:80/?foo=bar%3dbaz'))
1743 1747 'http://user:pw@host:80/?foo=bar%3dbaz'
1744 1748 >>> str(url('ssh://user:pw@[::1]:2200//home/joe#'))
1745 1749 'ssh://user:pw@[::1]:2200//home/joe#'
1746 1750 >>> str(url('http://localhost:80//'))
1747 1751 'http://localhost:80//'
1748 1752 >>> str(url('http://localhost:80/'))
1749 1753 'http://localhost:80/'
1750 1754 >>> str(url('http://localhost:80'))
1751 1755 'http://localhost:80/'
1752 1756 >>> str(url('bundle:foo'))
1753 1757 'bundle:foo'
1754 1758 >>> str(url('bundle://../foo'))
1755 1759 'bundle:../foo'
1756 1760 >>> str(url('path'))
1757 1761 'path'
1758 1762 >>> str(url('file:///tmp/foo/bar'))
1759 1763 'file:///tmp/foo/bar'
1760 1764 >>> str(url('file:///c:/tmp/foo/bar'))
1761 1765 'file:///c:/tmp/foo/bar'
1762 1766 >>> print url(r'bundle:foo\bar')
1763 1767 bundle:foo\bar
1764 1768 """
1765 1769 if self._localpath:
1766 1770 s = self.path
1767 1771 if self.scheme == 'bundle':
1768 1772 s = 'bundle:' + s
1769 1773 if self.fragment:
1770 1774 s += '#' + self.fragment
1771 1775 return s
1772 1776
1773 1777 s = self.scheme + ':'
1774 1778 if self.user or self.passwd or self.host:
1775 1779 s += '//'
1776 1780 elif self.scheme and (not self.path or self.path.startswith('/')
1777 1781 or hasdriveletter(self.path)):
1778 1782 s += '//'
1779 1783 if hasdriveletter(self.path):
1780 1784 s += '/'
1781 1785 if self.user:
1782 1786 s += urllib.quote(self.user, safe=self._safechars)
1783 1787 if self.passwd:
1784 1788 s += ':' + urllib.quote(self.passwd, safe=self._safechars)
1785 1789 if self.user or self.passwd:
1786 1790 s += '@'
1787 1791 if self.host:
1788 1792 if not (self.host.startswith('[') and self.host.endswith(']')):
1789 1793 s += urllib.quote(self.host)
1790 1794 else:
1791 1795 s += self.host
1792 1796 if self.port:
1793 1797 s += ':' + urllib.quote(self.port)
1794 1798 if self.host:
1795 1799 s += '/'
1796 1800 if self.path:
1797 1801 # TODO: similar to the query string, we should not unescape the
1798 1802 # path when we store it, the path might contain '%2f' = '/',
1799 1803 # which we should *not* escape.
1800 1804 s += urllib.quote(self.path, safe=self._safepchars)
1801 1805 if self.query:
1802 1806 # we store the query in escaped form.
1803 1807 s += '?' + self.query
1804 1808 if self.fragment is not None:
1805 1809 s += '#' + urllib.quote(self.fragment, safe=self._safepchars)
1806 1810 return s
1807 1811
1808 1812 def authinfo(self):
1809 1813 user, passwd = self.user, self.passwd
1810 1814 try:
1811 1815 self.user, self.passwd = None, None
1812 1816 s = str(self)
1813 1817 finally:
1814 1818 self.user, self.passwd = user, passwd
1815 1819 if not self.user:
1816 1820 return (s, None)
1817 1821 # authinfo[1] is passed to urllib2 password manager, and its
1818 1822 # URIs must not contain credentials. The host is passed in the
1819 1823 # URIs list because Python < 2.4.3 uses only that to search for
1820 1824 # a password.
1821 1825 return (s, (None, (s, self.host),
1822 1826 self.user, self.passwd or ''))
1823 1827
1824 1828 def isabs(self):
1825 1829 if self.scheme and self.scheme != 'file':
1826 1830 return True # remote URL
1827 1831 if hasdriveletter(self.path):
1828 1832 return True # absolute for our purposes - can't be joined()
1829 1833 if self.path.startswith(r'\\'):
1830 1834 return True # Windows UNC path
1831 1835 if self.path.startswith('/'):
1832 1836 return True # POSIX-style
1833 1837 return False
1834 1838
1835 1839 def localpath(self):
1836 1840 if self.scheme == 'file' or self.scheme == 'bundle':
1837 1841 path = self.path or '/'
1838 1842 # For Windows, we need to promote hosts containing drive
1839 1843 # letters to paths with drive letters.
1840 1844 if hasdriveletter(self._hostport):
1841 1845 path = self._hostport + '/' + self.path
1842 1846 elif (self.host is not None and self.path
1843 1847 and not hasdriveletter(path)):
1844 1848 path = '/' + path
1845 1849 return path
1846 1850 return self._origpath
1847 1851
1848 1852 def hasscheme(path):
1849 1853 return bool(url(path).scheme)
1850 1854
1851 1855 def hasdriveletter(path):
1852 1856 return path and path[1:2] == ':' and path[0:1].isalpha()
1853 1857
1854 1858 def urllocalpath(path):
1855 1859 return url(path, parsequery=False, parsefragment=False).localpath()
1856 1860
1857 1861 def hidepassword(u):
1858 1862 '''hide user credential in a url string'''
1859 1863 u = url(u)
1860 1864 if u.passwd:
1861 1865 u.passwd = '***'
1862 1866 return str(u)
1863 1867
1864 1868 def removeauth(u):
1865 1869 '''remove all authentication information from a url string'''
1866 1870 u = url(u)
1867 1871 u.user = u.passwd = None
1868 1872 return str(u)
1869 1873
1870 1874 def isatty(fd):
1871 1875 try:
1872 1876 return fd.isatty()
1873 1877 except AttributeError:
1874 1878 return False
1875 1879
1876 1880 timecount = unitcountfn(
1877 1881 (1, 1e3, _('%.0f s')),
1878 1882 (100, 1, _('%.1f s')),
1879 1883 (10, 1, _('%.2f s')),
1880 1884 (1, 1, _('%.3f s')),
1881 1885 (100, 0.001, _('%.1f ms')),
1882 1886 (10, 0.001, _('%.2f ms')),
1883 1887 (1, 0.001, _('%.3f ms')),
1884 1888 (100, 0.000001, _('%.1f us')),
1885 1889 (10, 0.000001, _('%.2f us')),
1886 1890 (1, 0.000001, _('%.3f us')),
1887 1891 (100, 0.000000001, _('%.1f ns')),
1888 1892 (10, 0.000000001, _('%.2f ns')),
1889 1893 (1, 0.000000001, _('%.3f ns')),
1890 1894 )
1891 1895
1892 1896 _timenesting = [0]
1893 1897
1894 1898 def timed(func):
1895 1899 '''Report the execution time of a function call to stderr.
1896 1900
1897 1901 During development, use as a decorator when you need to measure
1898 1902 the cost of a function, e.g. as follows:
1899 1903
1900 1904 @util.timed
1901 1905 def foo(a, b, c):
1902 1906 pass
1903 1907 '''
1904 1908
1905 1909 def wrapper(*args, **kwargs):
1906 1910 start = time.time()
1907 1911 indent = 2
1908 1912 _timenesting[0] += indent
1909 1913 try:
1910 1914 return func(*args, **kwargs)
1911 1915 finally:
1912 1916 elapsed = time.time() - start
1913 1917 _timenesting[0] -= indent
1914 1918 sys.stderr.write('%s%s: %s\n' %
1915 1919 (' ' * _timenesting[0], func.__name__,
1916 1920 timecount(elapsed)))
1917 1921 return wrapper
General Comments 0
You need to be logged in to leave comments. Login now