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