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