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