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