##// END OF EJS Templates
parsedate: add UTC and GMT timezones
Matt Mackall -
r3809:4d93b37b default
parent child Browse files
Show More
@@ -1,1216 +1,1218
1 """
1 """
2 util.py - Mercurial utility functions and platform specfic implementations
2 util.py - Mercurial utility functions and platform specfic implementations
3
3
4 Copyright 2005 K. Thananchayan <thananck@yahoo.com>
4 Copyright 2005 K. Thananchayan <thananck@yahoo.com>
5 Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
5 Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
6 Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
6 Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
7
7
8 This software may be used and distributed according to the terms
8 This software may be used and distributed according to the terms
9 of the GNU General Public License, incorporated herein by reference.
9 of the GNU General Public License, incorporated herein by reference.
10
10
11 This contains helper routines that are independent of the SCM core and hide
11 This contains helper routines that are independent of the SCM core and hide
12 platform-specific details from the core.
12 platform-specific details from the core.
13 """
13 """
14
14
15 from i18n import gettext as _
15 from i18n import gettext as _
16 from demandload import *
16 from demandload import *
17 demandload(globals(), "cStringIO errno getpass popen2 re shutil sys tempfile")
17 demandload(globals(), "cStringIO errno getpass popen2 re shutil sys tempfile")
18 demandload(globals(), "os threading time calendar ConfigParser locale")
18 demandload(globals(), "os threading time calendar ConfigParser locale")
19
19
20 _encoding = os.environ.get("HGENCODING") or locale.getpreferredencoding()
20 _encoding = os.environ.get("HGENCODING") or locale.getpreferredencoding()
21 _encodingmode = os.environ.get("HGENCODINGMODE", "strict")
21 _encodingmode = os.environ.get("HGENCODINGMODE", "strict")
22
22
23 def tolocal(s):
23 def tolocal(s):
24 """
24 """
25 Convert a string from internal UTF-8 to local encoding
25 Convert a string from internal UTF-8 to local encoding
26
26
27 All internal strings should be UTF-8 but some repos before the
27 All internal strings should be UTF-8 but some repos before the
28 implementation of locale support may contain latin1 or possibly
28 implementation of locale support may contain latin1 or possibly
29 other character sets. We attempt to decode everything strictly
29 other character sets. We attempt to decode everything strictly
30 using UTF-8, then Latin-1, and failing that, we use UTF-8 and
30 using UTF-8, then Latin-1, and failing that, we use UTF-8 and
31 replace unknown characters.
31 replace unknown characters.
32 """
32 """
33 for e in "utf-8 latin1".split():
33 for e in "utf-8 latin1".split():
34 try:
34 try:
35 u = s.decode(e) # attempt strict decoding
35 u = s.decode(e) # attempt strict decoding
36 return u.encode(_encoding, "replace")
36 return u.encode(_encoding, "replace")
37 except UnicodeDecodeError:
37 except UnicodeDecodeError:
38 pass
38 pass
39 u = s.decode("utf-8", "replace") # last ditch
39 u = s.decode("utf-8", "replace") # last ditch
40 return u.encode(_encoding, "replace")
40 return u.encode(_encoding, "replace")
41
41
42 def fromlocal(s):
42 def fromlocal(s):
43 """
43 """
44 Convert a string from the local character encoding to UTF-8
44 Convert a string from the local character encoding to UTF-8
45
45
46 We attempt to decode strings using the encoding mode set by
46 We attempt to decode strings using the encoding mode set by
47 HG_ENCODINGMODE, which defaults to 'strict'. In this mode, unknown
47 HG_ENCODINGMODE, which defaults to 'strict'. In this mode, unknown
48 characters will cause an error message. Other modes include
48 characters will cause an error message. Other modes include
49 'replace', which replaces unknown characters with a special
49 'replace', which replaces unknown characters with a special
50 Unicode character, and 'ignore', which drops the character.
50 Unicode character, and 'ignore', which drops the character.
51 """
51 """
52 try:
52 try:
53 return s.decode(_encoding, _encodingmode).encode("utf-8")
53 return s.decode(_encoding, _encodingmode).encode("utf-8")
54 except UnicodeDecodeError, inst:
54 except UnicodeDecodeError, inst:
55 sub = s[max(0, inst.start-10):inst.start+10]
55 sub = s[max(0, inst.start-10):inst.start+10]
56 raise Abort("decoding near '%s': %s!\n" % (sub, inst))
56 raise Abort("decoding near '%s': %s!\n" % (sub, inst))
57
57
58 def locallen(s):
58 def locallen(s):
59 """Find the length in characters of a local string"""
59 """Find the length in characters of a local string"""
60 return len(s.decode(_encoding, "replace"))
60 return len(s.decode(_encoding, "replace"))
61
61
62 def localsub(s, a, b=None):
62 def localsub(s, a, b=None):
63 try:
63 try:
64 u = s.decode(_encoding, _encodingmode)
64 u = s.decode(_encoding, _encodingmode)
65 if b is not None:
65 if b is not None:
66 u = u[a:b]
66 u = u[a:b]
67 else:
67 else:
68 u = u[:a]
68 u = u[:a]
69 return u.encode(_encoding, _encodingmode)
69 return u.encode(_encoding, _encodingmode)
70 except UnicodeDecodeError, inst:
70 except UnicodeDecodeError, inst:
71 sub = s[max(0, inst.start-10), inst.start+10]
71 sub = s[max(0, inst.start-10), inst.start+10]
72 raise Abort("decoding near '%s': %s!\n" % (sub, inst))
72 raise Abort("decoding near '%s': %s!\n" % (sub, inst))
73
73
74 # used by parsedate
74 # used by parsedate
75 defaultdateformats = (
75 defaultdateformats = (
76 '%Y-%m-%d %H:%M:%S',
76 '%Y-%m-%d %H:%M:%S',
77 '%Y-%m-%d %I:%M:%S%p',
77 '%Y-%m-%d %I:%M:%S%p',
78 '%Y-%m-%d %H:%M',
78 '%Y-%m-%d %H:%M',
79 '%Y-%m-%d %I:%M%p',
79 '%Y-%m-%d %I:%M%p',
80 '%Y-%m-%d',
80 '%Y-%m-%d',
81 '%m-%d',
81 '%m-%d',
82 '%m/%d',
82 '%m/%d',
83 '%m/%d/%y',
83 '%m/%d/%y',
84 '%m/%d/%Y',
84 '%m/%d/%Y',
85 '%a %b %d %H:%M:%S %Y',
85 '%a %b %d %H:%M:%S %Y',
86 '%a %b %d %I:%M:%S%p %Y',
86 '%a %b %d %I:%M:%S%p %Y',
87 '%b %d %H:%M:%S %Y',
87 '%b %d %H:%M:%S %Y',
88 '%b %d %I:%M:%S%p',
88 '%b %d %I:%M:%S%p',
89 '%b %d %H:%M',
89 '%b %d %H:%M',
90 '%b %d %I:%M%p',
90 '%b %d %I:%M%p',
91 '%b %d %Y',
91 '%b %d %Y',
92 '%b %d',
92 '%b %d',
93 '%H:%M:%S',
93 '%H:%M:%S',
94 '%I:%M:%SP',
94 '%I:%M:%SP',
95 '%H:%M',
95 '%H:%M',
96 '%I:%M%p',
96 '%I:%M%p',
97 )
97 )
98
98
99 class SignalInterrupt(Exception):
99 class SignalInterrupt(Exception):
100 """Exception raised on SIGTERM and SIGHUP."""
100 """Exception raised on SIGTERM and SIGHUP."""
101
101
102 # like SafeConfigParser but with case-sensitive keys
102 # like SafeConfigParser but with case-sensitive keys
103 class configparser(ConfigParser.SafeConfigParser):
103 class configparser(ConfigParser.SafeConfigParser):
104 def optionxform(self, optionstr):
104 def optionxform(self, optionstr):
105 return optionstr
105 return optionstr
106
106
107 def cachefunc(func):
107 def cachefunc(func):
108 '''cache the result of function calls'''
108 '''cache the result of function calls'''
109 # XXX doesn't handle keywords args
109 # XXX doesn't handle keywords args
110 cache = {}
110 cache = {}
111 if func.func_code.co_argcount == 1:
111 if func.func_code.co_argcount == 1:
112 # we gain a small amount of time because
112 # we gain a small amount of time because
113 # we don't need to pack/unpack the list
113 # we don't need to pack/unpack the list
114 def f(arg):
114 def f(arg):
115 if arg not in cache:
115 if arg not in cache:
116 cache[arg] = func(arg)
116 cache[arg] = func(arg)
117 return cache[arg]
117 return cache[arg]
118 else:
118 else:
119 def f(*args):
119 def f(*args):
120 if args not in cache:
120 if args not in cache:
121 cache[args] = func(*args)
121 cache[args] = func(*args)
122 return cache[args]
122 return cache[args]
123
123
124 return f
124 return f
125
125
126 def pipefilter(s, cmd):
126 def pipefilter(s, cmd):
127 '''filter string S through command CMD, returning its output'''
127 '''filter string S through command CMD, returning its output'''
128 (pout, pin) = popen2.popen2(cmd, -1, 'b')
128 (pout, pin) = popen2.popen2(cmd, -1, 'b')
129 def writer():
129 def writer():
130 try:
130 try:
131 pin.write(s)
131 pin.write(s)
132 pin.close()
132 pin.close()
133 except IOError, inst:
133 except IOError, inst:
134 if inst.errno != errno.EPIPE:
134 if inst.errno != errno.EPIPE:
135 raise
135 raise
136
136
137 # we should use select instead on UNIX, but this will work on most
137 # we should use select instead on UNIX, but this will work on most
138 # systems, including Windows
138 # systems, including Windows
139 w = threading.Thread(target=writer)
139 w = threading.Thread(target=writer)
140 w.start()
140 w.start()
141 f = pout.read()
141 f = pout.read()
142 pout.close()
142 pout.close()
143 w.join()
143 w.join()
144 return f
144 return f
145
145
146 def tempfilter(s, cmd):
146 def tempfilter(s, cmd):
147 '''filter string S through a pair of temporary files with CMD.
147 '''filter string S through a pair of temporary files with CMD.
148 CMD is used as a template to create the real command to be run,
148 CMD is used as a template to create the real command to be run,
149 with the strings INFILE and OUTFILE replaced by the real names of
149 with the strings INFILE and OUTFILE replaced by the real names of
150 the temporary files generated.'''
150 the temporary files generated.'''
151 inname, outname = None, None
151 inname, outname = None, None
152 try:
152 try:
153 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
153 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
154 fp = os.fdopen(infd, 'wb')
154 fp = os.fdopen(infd, 'wb')
155 fp.write(s)
155 fp.write(s)
156 fp.close()
156 fp.close()
157 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
157 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
158 os.close(outfd)
158 os.close(outfd)
159 cmd = cmd.replace('INFILE', inname)
159 cmd = cmd.replace('INFILE', inname)
160 cmd = cmd.replace('OUTFILE', outname)
160 cmd = cmd.replace('OUTFILE', outname)
161 code = os.system(cmd)
161 code = os.system(cmd)
162 if code: raise Abort(_("command '%s' failed: %s") %
162 if code: raise Abort(_("command '%s' failed: %s") %
163 (cmd, explain_exit(code)))
163 (cmd, explain_exit(code)))
164 return open(outname, 'rb').read()
164 return open(outname, 'rb').read()
165 finally:
165 finally:
166 try:
166 try:
167 if inname: os.unlink(inname)
167 if inname: os.unlink(inname)
168 except: pass
168 except: pass
169 try:
169 try:
170 if outname: os.unlink(outname)
170 if outname: os.unlink(outname)
171 except: pass
171 except: pass
172
172
173 filtertable = {
173 filtertable = {
174 'tempfile:': tempfilter,
174 'tempfile:': tempfilter,
175 'pipe:': pipefilter,
175 'pipe:': pipefilter,
176 }
176 }
177
177
178 def filter(s, cmd):
178 def filter(s, cmd):
179 "filter a string through a command that transforms its input to its output"
179 "filter a string through a command that transforms its input to its output"
180 for name, fn in filtertable.iteritems():
180 for name, fn in filtertable.iteritems():
181 if cmd.startswith(name):
181 if cmd.startswith(name):
182 return fn(s, cmd[len(name):].lstrip())
182 return fn(s, cmd[len(name):].lstrip())
183 return pipefilter(s, cmd)
183 return pipefilter(s, cmd)
184
184
185 def find_in_path(name, path, default=None):
185 def find_in_path(name, path, default=None):
186 '''find name in search path. path can be string (will be split
186 '''find name in search path. path can be string (will be split
187 with os.pathsep), or iterable thing that returns strings. if name
187 with os.pathsep), or iterable thing that returns strings. if name
188 found, return path to name. else return default.'''
188 found, return path to name. else return default.'''
189 if isinstance(path, str):
189 if isinstance(path, str):
190 path = path.split(os.pathsep)
190 path = path.split(os.pathsep)
191 for p in path:
191 for p in path:
192 p_name = os.path.join(p, name)
192 p_name = os.path.join(p, name)
193 if os.path.exists(p_name):
193 if os.path.exists(p_name):
194 return p_name
194 return p_name
195 return default
195 return default
196
196
197 def binary(s):
197 def binary(s):
198 """return true if a string is binary data using diff's heuristic"""
198 """return true if a string is binary data using diff's heuristic"""
199 if s and '\0' in s[:4096]:
199 if s and '\0' in s[:4096]:
200 return True
200 return True
201 return False
201 return False
202
202
203 def unique(g):
203 def unique(g):
204 """return the uniq elements of iterable g"""
204 """return the uniq elements of iterable g"""
205 seen = {}
205 seen = {}
206 l = []
206 l = []
207 for f in g:
207 for f in g:
208 if f not in seen:
208 if f not in seen:
209 seen[f] = 1
209 seen[f] = 1
210 l.append(f)
210 l.append(f)
211 return l
211 return l
212
212
213 class Abort(Exception):
213 class Abort(Exception):
214 """Raised if a command needs to print an error and exit."""
214 """Raised if a command needs to print an error and exit."""
215
215
216 class UnexpectedOutput(Abort):
216 class UnexpectedOutput(Abort):
217 """Raised to print an error with part of output and exit."""
217 """Raised to print an error with part of output and exit."""
218
218
219 def always(fn): return True
219 def always(fn): return True
220 def never(fn): return False
220 def never(fn): return False
221
221
222 def patkind(name, dflt_pat='glob'):
222 def patkind(name, dflt_pat='glob'):
223 """Split a string into an optional pattern kind prefix and the
223 """Split a string into an optional pattern kind prefix and the
224 actual pattern."""
224 actual pattern."""
225 for prefix in 're', 'glob', 'path', 'relglob', 'relpath', 'relre':
225 for prefix in 're', 'glob', 'path', 'relglob', 'relpath', 'relre':
226 if name.startswith(prefix + ':'): return name.split(':', 1)
226 if name.startswith(prefix + ':'): return name.split(':', 1)
227 return dflt_pat, name
227 return dflt_pat, name
228
228
229 def globre(pat, head='^', tail='$'):
229 def globre(pat, head='^', tail='$'):
230 "convert a glob pattern into a regexp"
230 "convert a glob pattern into a regexp"
231 i, n = 0, len(pat)
231 i, n = 0, len(pat)
232 res = ''
232 res = ''
233 group = False
233 group = False
234 def peek(): return i < n and pat[i]
234 def peek(): return i < n and pat[i]
235 while i < n:
235 while i < n:
236 c = pat[i]
236 c = pat[i]
237 i = i+1
237 i = i+1
238 if c == '*':
238 if c == '*':
239 if peek() == '*':
239 if peek() == '*':
240 i += 1
240 i += 1
241 res += '.*'
241 res += '.*'
242 else:
242 else:
243 res += '[^/]*'
243 res += '[^/]*'
244 elif c == '?':
244 elif c == '?':
245 res += '.'
245 res += '.'
246 elif c == '[':
246 elif c == '[':
247 j = i
247 j = i
248 if j < n and pat[j] in '!]':
248 if j < n and pat[j] in '!]':
249 j += 1
249 j += 1
250 while j < n and pat[j] != ']':
250 while j < n and pat[j] != ']':
251 j += 1
251 j += 1
252 if j >= n:
252 if j >= n:
253 res += '\\['
253 res += '\\['
254 else:
254 else:
255 stuff = pat[i:j].replace('\\','\\\\')
255 stuff = pat[i:j].replace('\\','\\\\')
256 i = j + 1
256 i = j + 1
257 if stuff[0] == '!':
257 if stuff[0] == '!':
258 stuff = '^' + stuff[1:]
258 stuff = '^' + stuff[1:]
259 elif stuff[0] == '^':
259 elif stuff[0] == '^':
260 stuff = '\\' + stuff
260 stuff = '\\' + stuff
261 res = '%s[%s]' % (res, stuff)
261 res = '%s[%s]' % (res, stuff)
262 elif c == '{':
262 elif c == '{':
263 group = True
263 group = True
264 res += '(?:'
264 res += '(?:'
265 elif c == '}' and group:
265 elif c == '}' and group:
266 res += ')'
266 res += ')'
267 group = False
267 group = False
268 elif c == ',' and group:
268 elif c == ',' and group:
269 res += '|'
269 res += '|'
270 elif c == '\\':
270 elif c == '\\':
271 p = peek()
271 p = peek()
272 if p:
272 if p:
273 i += 1
273 i += 1
274 res += re.escape(p)
274 res += re.escape(p)
275 else:
275 else:
276 res += re.escape(c)
276 res += re.escape(c)
277 else:
277 else:
278 res += re.escape(c)
278 res += re.escape(c)
279 return head + res + tail
279 return head + res + tail
280
280
281 _globchars = {'[': 1, '{': 1, '*': 1, '?': 1}
281 _globchars = {'[': 1, '{': 1, '*': 1, '?': 1}
282
282
283 def pathto(n1, n2):
283 def pathto(n1, n2):
284 '''return the relative path from one place to another.
284 '''return the relative path from one place to another.
285 n1 should use os.sep to separate directories
285 n1 should use os.sep to separate directories
286 n2 should use "/" to separate directories
286 n2 should use "/" to separate directories
287 returns an os.sep-separated path.
287 returns an os.sep-separated path.
288 '''
288 '''
289 if not n1: return localpath(n2)
289 if not n1: return localpath(n2)
290 a, b = n1.split(os.sep), n2.split('/')
290 a, b = n1.split(os.sep), n2.split('/')
291 a.reverse()
291 a.reverse()
292 b.reverse()
292 b.reverse()
293 while a and b and a[-1] == b[-1]:
293 while a and b and a[-1] == b[-1]:
294 a.pop()
294 a.pop()
295 b.pop()
295 b.pop()
296 b.reverse()
296 b.reverse()
297 return os.sep.join((['..'] * len(a)) + b)
297 return os.sep.join((['..'] * len(a)) + b)
298
298
299 def canonpath(root, cwd, myname):
299 def canonpath(root, cwd, myname):
300 """return the canonical path of myname, given cwd and root"""
300 """return the canonical path of myname, given cwd and root"""
301 if root == os.sep:
301 if root == os.sep:
302 rootsep = os.sep
302 rootsep = os.sep
303 elif root.endswith(os.sep):
303 elif root.endswith(os.sep):
304 rootsep = root
304 rootsep = root
305 else:
305 else:
306 rootsep = root + os.sep
306 rootsep = root + os.sep
307 name = myname
307 name = myname
308 if not os.path.isabs(name):
308 if not os.path.isabs(name):
309 name = os.path.join(root, cwd, name)
309 name = os.path.join(root, cwd, name)
310 name = os.path.normpath(name)
310 name = os.path.normpath(name)
311 if name != rootsep and name.startswith(rootsep):
311 if name != rootsep and name.startswith(rootsep):
312 name = name[len(rootsep):]
312 name = name[len(rootsep):]
313 audit_path(name)
313 audit_path(name)
314 return pconvert(name)
314 return pconvert(name)
315 elif name == root:
315 elif name == root:
316 return ''
316 return ''
317 else:
317 else:
318 # Determine whether `name' is in the hierarchy at or beneath `root',
318 # Determine whether `name' is in the hierarchy at or beneath `root',
319 # by iterating name=dirname(name) until that causes no change (can't
319 # by iterating name=dirname(name) until that causes no change (can't
320 # check name == '/', because that doesn't work on windows). For each
320 # check name == '/', because that doesn't work on windows). For each
321 # `name', compare dev/inode numbers. If they match, the list `rel'
321 # `name', compare dev/inode numbers. If they match, the list `rel'
322 # holds the reversed list of components making up the relative file
322 # holds the reversed list of components making up the relative file
323 # name we want.
323 # name we want.
324 root_st = os.stat(root)
324 root_st = os.stat(root)
325 rel = []
325 rel = []
326 while True:
326 while True:
327 try:
327 try:
328 name_st = os.stat(name)
328 name_st = os.stat(name)
329 except OSError:
329 except OSError:
330 break
330 break
331 if samestat(name_st, root_st):
331 if samestat(name_st, root_st):
332 rel.reverse()
332 rel.reverse()
333 name = os.path.join(*rel)
333 name = os.path.join(*rel)
334 audit_path(name)
334 audit_path(name)
335 return pconvert(name)
335 return pconvert(name)
336 dirname, basename = os.path.split(name)
336 dirname, basename = os.path.split(name)
337 rel.append(basename)
337 rel.append(basename)
338 if dirname == name:
338 if dirname == name:
339 break
339 break
340 name = dirname
340 name = dirname
341
341
342 raise Abort('%s not under root' % myname)
342 raise Abort('%s not under root' % myname)
343
343
344 def matcher(canonroot, cwd='', names=['.'], inc=[], exc=[], head='', src=None):
344 def matcher(canonroot, cwd='', names=['.'], inc=[], exc=[], head='', src=None):
345 return _matcher(canonroot, cwd, names, inc, exc, head, 'glob', src)
345 return _matcher(canonroot, cwd, names, inc, exc, head, 'glob', src)
346
346
347 def cmdmatcher(canonroot, cwd='', names=['.'], inc=[], exc=[], head='', src=None):
347 def cmdmatcher(canonroot, cwd='', names=['.'], inc=[], exc=[], head='', src=None):
348 if os.name == 'nt':
348 if os.name == 'nt':
349 dflt_pat = 'glob'
349 dflt_pat = 'glob'
350 else:
350 else:
351 dflt_pat = 'relpath'
351 dflt_pat = 'relpath'
352 return _matcher(canonroot, cwd, names, inc, exc, head, dflt_pat, src)
352 return _matcher(canonroot, cwd, names, inc, exc, head, dflt_pat, src)
353
353
354 def _matcher(canonroot, cwd, names, inc, exc, head, dflt_pat, src):
354 def _matcher(canonroot, cwd, names, inc, exc, head, dflt_pat, src):
355 """build a function to match a set of file patterns
355 """build a function to match a set of file patterns
356
356
357 arguments:
357 arguments:
358 canonroot - the canonical root of the tree you're matching against
358 canonroot - the canonical root of the tree you're matching against
359 cwd - the current working directory, if relevant
359 cwd - the current working directory, if relevant
360 names - patterns to find
360 names - patterns to find
361 inc - patterns to include
361 inc - patterns to include
362 exc - patterns to exclude
362 exc - patterns to exclude
363 head - a regex to prepend to patterns to control whether a match is rooted
363 head - a regex to prepend to patterns to control whether a match is rooted
364
364
365 a pattern is one of:
365 a pattern is one of:
366 'glob:<rooted glob>'
366 'glob:<rooted glob>'
367 're:<rooted regexp>'
367 're:<rooted regexp>'
368 'path:<rooted path>'
368 'path:<rooted path>'
369 'relglob:<relative glob>'
369 'relglob:<relative glob>'
370 'relpath:<relative path>'
370 'relpath:<relative path>'
371 'relre:<relative regexp>'
371 'relre:<relative regexp>'
372 '<rooted path or regexp>'
372 '<rooted path or regexp>'
373
373
374 returns:
374 returns:
375 a 3-tuple containing
375 a 3-tuple containing
376 - list of explicit non-pattern names passed in
376 - list of explicit non-pattern names passed in
377 - a bool match(filename) function
377 - a bool match(filename) function
378 - a bool indicating if any patterns were passed in
378 - a bool indicating if any patterns were passed in
379
379
380 todo:
380 todo:
381 make head regex a rooted bool
381 make head regex a rooted bool
382 """
382 """
383
383
384 def contains_glob(name):
384 def contains_glob(name):
385 for c in name:
385 for c in name:
386 if c in _globchars: return True
386 if c in _globchars: return True
387 return False
387 return False
388
388
389 def regex(kind, name, tail):
389 def regex(kind, name, tail):
390 '''convert a pattern into a regular expression'''
390 '''convert a pattern into a regular expression'''
391 if kind == 're':
391 if kind == 're':
392 return name
392 return name
393 elif kind == 'path':
393 elif kind == 'path':
394 return '^' + re.escape(name) + '(?:/|$)'
394 return '^' + re.escape(name) + '(?:/|$)'
395 elif kind == 'relglob':
395 elif kind == 'relglob':
396 return head + globre(name, '(?:|.*/)', tail)
396 return head + globre(name, '(?:|.*/)', tail)
397 elif kind == 'relpath':
397 elif kind == 'relpath':
398 return head + re.escape(name) + tail
398 return head + re.escape(name) + tail
399 elif kind == 'relre':
399 elif kind == 'relre':
400 if name.startswith('^'):
400 if name.startswith('^'):
401 return name
401 return name
402 return '.*' + name
402 return '.*' + name
403 return head + globre(name, '', tail)
403 return head + globre(name, '', tail)
404
404
405 def matchfn(pats, tail):
405 def matchfn(pats, tail):
406 """build a matching function from a set of patterns"""
406 """build a matching function from a set of patterns"""
407 if not pats:
407 if not pats:
408 return
408 return
409 matches = []
409 matches = []
410 for k, p in pats:
410 for k, p in pats:
411 try:
411 try:
412 pat = '(?:%s)' % regex(k, p, tail)
412 pat = '(?:%s)' % regex(k, p, tail)
413 matches.append(re.compile(pat).match)
413 matches.append(re.compile(pat).match)
414 except re.error:
414 except re.error:
415 if src: raise Abort("%s: invalid pattern (%s): %s" % (src, k, p))
415 if src: raise Abort("%s: invalid pattern (%s): %s" % (src, k, p))
416 else: raise Abort("invalid pattern (%s): %s" % (k, p))
416 else: raise Abort("invalid pattern (%s): %s" % (k, p))
417
417
418 def buildfn(text):
418 def buildfn(text):
419 for m in matches:
419 for m in matches:
420 r = m(text)
420 r = m(text)
421 if r:
421 if r:
422 return r
422 return r
423
423
424 return buildfn
424 return buildfn
425
425
426 def globprefix(pat):
426 def globprefix(pat):
427 '''return the non-glob prefix of a path, e.g. foo/* -> foo'''
427 '''return the non-glob prefix of a path, e.g. foo/* -> foo'''
428 root = []
428 root = []
429 for p in pat.split(os.sep):
429 for p in pat.split(os.sep):
430 if contains_glob(p): break
430 if contains_glob(p): break
431 root.append(p)
431 root.append(p)
432 return '/'.join(root)
432 return '/'.join(root)
433
433
434 pats = []
434 pats = []
435 files = []
435 files = []
436 roots = []
436 roots = []
437 for kind, name in [patkind(p, dflt_pat) for p in names]:
437 for kind, name in [patkind(p, dflt_pat) for p in names]:
438 if kind in ('glob', 'relpath'):
438 if kind in ('glob', 'relpath'):
439 name = canonpath(canonroot, cwd, name)
439 name = canonpath(canonroot, cwd, name)
440 if name == '':
440 if name == '':
441 kind, name = 'glob', '**'
441 kind, name = 'glob', '**'
442 if kind in ('glob', 'path', 're'):
442 if kind in ('glob', 'path', 're'):
443 pats.append((kind, name))
443 pats.append((kind, name))
444 if kind == 'glob':
444 if kind == 'glob':
445 root = globprefix(name)
445 root = globprefix(name)
446 if root: roots.append(root)
446 if root: roots.append(root)
447 elif kind == 'relpath':
447 elif kind == 'relpath':
448 files.append((kind, name))
448 files.append((kind, name))
449 roots.append(name)
449 roots.append(name)
450
450
451 patmatch = matchfn(pats, '$') or always
451 patmatch = matchfn(pats, '$') or always
452 filematch = matchfn(files, '(?:/|$)') or always
452 filematch = matchfn(files, '(?:/|$)') or always
453 incmatch = always
453 incmatch = always
454 if inc:
454 if inc:
455 inckinds = [patkind(canonpath(canonroot, cwd, i)) for i in inc]
455 inckinds = [patkind(canonpath(canonroot, cwd, i)) for i in inc]
456 incmatch = matchfn(inckinds, '(?:/|$)')
456 incmatch = matchfn(inckinds, '(?:/|$)')
457 excmatch = lambda fn: False
457 excmatch = lambda fn: False
458 if exc:
458 if exc:
459 exckinds = [patkind(canonpath(canonroot, cwd, x)) for x in exc]
459 exckinds = [patkind(canonpath(canonroot, cwd, x)) for x in exc]
460 excmatch = matchfn(exckinds, '(?:/|$)')
460 excmatch = matchfn(exckinds, '(?:/|$)')
461
461
462 return (roots,
462 return (roots,
463 lambda fn: (incmatch(fn) and not excmatch(fn) and
463 lambda fn: (incmatch(fn) and not excmatch(fn) and
464 (fn.endswith('/') or
464 (fn.endswith('/') or
465 (not pats and not files) or
465 (not pats and not files) or
466 (pats and patmatch(fn)) or
466 (pats and patmatch(fn)) or
467 (files and filematch(fn)))),
467 (files and filematch(fn)))),
468 (inc or exc or (pats and pats != [('glob', '**')])) and True)
468 (inc or exc or (pats and pats != [('glob', '**')])) and True)
469
469
470 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None):
470 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None):
471 '''enhanced shell command execution.
471 '''enhanced shell command execution.
472 run with environment maybe modified, maybe in different dir.
472 run with environment maybe modified, maybe in different dir.
473
473
474 if command fails and onerr is None, return status. if ui object,
474 if command fails and onerr is None, return status. if ui object,
475 print error message and return status, else raise onerr object as
475 print error message and return status, else raise onerr object as
476 exception.'''
476 exception.'''
477 def py2shell(val):
477 def py2shell(val):
478 'convert python object into string that is useful to shell'
478 'convert python object into string that is useful to shell'
479 if val in (None, False):
479 if val in (None, False):
480 return '0'
480 return '0'
481 if val == True:
481 if val == True:
482 return '1'
482 return '1'
483 return str(val)
483 return str(val)
484 oldenv = {}
484 oldenv = {}
485 for k in environ:
485 for k in environ:
486 oldenv[k] = os.environ.get(k)
486 oldenv[k] = os.environ.get(k)
487 if cwd is not None:
487 if cwd is not None:
488 oldcwd = os.getcwd()
488 oldcwd = os.getcwd()
489 try:
489 try:
490 for k, v in environ.iteritems():
490 for k, v in environ.iteritems():
491 os.environ[k] = py2shell(v)
491 os.environ[k] = py2shell(v)
492 if cwd is not None and oldcwd != cwd:
492 if cwd is not None and oldcwd != cwd:
493 os.chdir(cwd)
493 os.chdir(cwd)
494 rc = os.system(cmd)
494 rc = os.system(cmd)
495 if rc and onerr:
495 if rc and onerr:
496 errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]),
496 errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]),
497 explain_exit(rc)[0])
497 explain_exit(rc)[0])
498 if errprefix:
498 if errprefix:
499 errmsg = '%s: %s' % (errprefix, errmsg)
499 errmsg = '%s: %s' % (errprefix, errmsg)
500 try:
500 try:
501 onerr.warn(errmsg + '\n')
501 onerr.warn(errmsg + '\n')
502 except AttributeError:
502 except AttributeError:
503 raise onerr(errmsg)
503 raise onerr(errmsg)
504 return rc
504 return rc
505 finally:
505 finally:
506 for k, v in oldenv.iteritems():
506 for k, v in oldenv.iteritems():
507 if v is None:
507 if v is None:
508 del os.environ[k]
508 del os.environ[k]
509 else:
509 else:
510 os.environ[k] = v
510 os.environ[k] = v
511 if cwd is not None and oldcwd != cwd:
511 if cwd is not None and oldcwd != cwd:
512 os.chdir(oldcwd)
512 os.chdir(oldcwd)
513
513
514 def rename(src, dst):
514 def rename(src, dst):
515 """forcibly rename a file"""
515 """forcibly rename a file"""
516 try:
516 try:
517 os.rename(src, dst)
517 os.rename(src, dst)
518 except OSError, err:
518 except OSError, err:
519 # on windows, rename to existing file is not allowed, so we
519 # on windows, rename to existing file is not allowed, so we
520 # must delete destination first. but if file is open, unlink
520 # must delete destination first. but if file is open, unlink
521 # schedules it for delete but does not delete it. rename
521 # schedules it for delete but does not delete it. rename
522 # happens immediately even for open files, so we create
522 # happens immediately even for open files, so we create
523 # temporary file, delete it, rename destination to that name,
523 # temporary file, delete it, rename destination to that name,
524 # then delete that. then rename is safe to do.
524 # then delete that. then rename is safe to do.
525 fd, temp = tempfile.mkstemp(dir=os.path.dirname(dst) or '.')
525 fd, temp = tempfile.mkstemp(dir=os.path.dirname(dst) or '.')
526 os.close(fd)
526 os.close(fd)
527 os.unlink(temp)
527 os.unlink(temp)
528 os.rename(dst, temp)
528 os.rename(dst, temp)
529 os.unlink(temp)
529 os.unlink(temp)
530 os.rename(src, dst)
530 os.rename(src, dst)
531
531
532 def unlink(f):
532 def unlink(f):
533 """unlink and remove the directory if it is empty"""
533 """unlink and remove the directory if it is empty"""
534 os.unlink(f)
534 os.unlink(f)
535 # try removing directories that might now be empty
535 # try removing directories that might now be empty
536 try:
536 try:
537 os.removedirs(os.path.dirname(f))
537 os.removedirs(os.path.dirname(f))
538 except OSError:
538 except OSError:
539 pass
539 pass
540
540
541 def copyfile(src, dest):
541 def copyfile(src, dest):
542 "copy a file, preserving mode"
542 "copy a file, preserving mode"
543 try:
543 try:
544 shutil.copyfile(src, dest)
544 shutil.copyfile(src, dest)
545 shutil.copymode(src, dest)
545 shutil.copymode(src, dest)
546 except shutil.Error, inst:
546 except shutil.Error, inst:
547 raise util.Abort(str(inst))
547 raise util.Abort(str(inst))
548
548
549 def copyfiles(src, dst, hardlink=None):
549 def copyfiles(src, dst, hardlink=None):
550 """Copy a directory tree using hardlinks if possible"""
550 """Copy a directory tree using hardlinks if possible"""
551
551
552 if hardlink is None:
552 if hardlink is None:
553 hardlink = (os.stat(src).st_dev ==
553 hardlink = (os.stat(src).st_dev ==
554 os.stat(os.path.dirname(dst)).st_dev)
554 os.stat(os.path.dirname(dst)).st_dev)
555
555
556 if os.path.isdir(src):
556 if os.path.isdir(src):
557 os.mkdir(dst)
557 os.mkdir(dst)
558 for name in os.listdir(src):
558 for name in os.listdir(src):
559 srcname = os.path.join(src, name)
559 srcname = os.path.join(src, name)
560 dstname = os.path.join(dst, name)
560 dstname = os.path.join(dst, name)
561 copyfiles(srcname, dstname, hardlink)
561 copyfiles(srcname, dstname, hardlink)
562 else:
562 else:
563 if hardlink:
563 if hardlink:
564 try:
564 try:
565 os_link(src, dst)
565 os_link(src, dst)
566 except (IOError, OSError):
566 except (IOError, OSError):
567 hardlink = False
567 hardlink = False
568 shutil.copy(src, dst)
568 shutil.copy(src, dst)
569 else:
569 else:
570 shutil.copy(src, dst)
570 shutil.copy(src, dst)
571
571
572 def audit_path(path):
572 def audit_path(path):
573 """Abort if path contains dangerous components"""
573 """Abort if path contains dangerous components"""
574 parts = os.path.normcase(path).split(os.sep)
574 parts = os.path.normcase(path).split(os.sep)
575 if (os.path.splitdrive(path)[0] or parts[0] in ('.hg', '')
575 if (os.path.splitdrive(path)[0] or parts[0] in ('.hg', '')
576 or os.pardir in parts):
576 or os.pardir in parts):
577 raise Abort(_("path contains illegal component: %s\n") % path)
577 raise Abort(_("path contains illegal component: %s\n") % path)
578
578
579 def _makelock_file(info, pathname):
579 def _makelock_file(info, pathname):
580 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
580 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
581 os.write(ld, info)
581 os.write(ld, info)
582 os.close(ld)
582 os.close(ld)
583
583
584 def _readlock_file(pathname):
584 def _readlock_file(pathname):
585 return posixfile(pathname).read()
585 return posixfile(pathname).read()
586
586
587 def nlinks(pathname):
587 def nlinks(pathname):
588 """Return number of hardlinks for the given file."""
588 """Return number of hardlinks for the given file."""
589 return os.lstat(pathname).st_nlink
589 return os.lstat(pathname).st_nlink
590
590
591 if hasattr(os, 'link'):
591 if hasattr(os, 'link'):
592 os_link = os.link
592 os_link = os.link
593 else:
593 else:
594 def os_link(src, dst):
594 def os_link(src, dst):
595 raise OSError(0, _("Hardlinks not supported"))
595 raise OSError(0, _("Hardlinks not supported"))
596
596
597 def fstat(fp):
597 def fstat(fp):
598 '''stat file object that may not have fileno method.'''
598 '''stat file object that may not have fileno method.'''
599 try:
599 try:
600 return os.fstat(fp.fileno())
600 return os.fstat(fp.fileno())
601 except AttributeError:
601 except AttributeError:
602 return os.stat(fp.name)
602 return os.stat(fp.name)
603
603
604 posixfile = file
604 posixfile = file
605
605
606 def is_win_9x():
606 def is_win_9x():
607 '''return true if run on windows 95, 98 or me.'''
607 '''return true if run on windows 95, 98 or me.'''
608 try:
608 try:
609 return sys.getwindowsversion()[3] == 1
609 return sys.getwindowsversion()[3] == 1
610 except AttributeError:
610 except AttributeError:
611 return os.name == 'nt' and 'command' in os.environ.get('comspec', '')
611 return os.name == 'nt' and 'command' in os.environ.get('comspec', '')
612
612
613 getuser_fallback = None
613 getuser_fallback = None
614
614
615 def getuser():
615 def getuser():
616 '''return name of current user'''
616 '''return name of current user'''
617 try:
617 try:
618 return getpass.getuser()
618 return getpass.getuser()
619 except ImportError:
619 except ImportError:
620 # import of pwd will fail on windows - try fallback
620 # import of pwd will fail on windows - try fallback
621 if getuser_fallback:
621 if getuser_fallback:
622 return getuser_fallback()
622 return getuser_fallback()
623 # raised if win32api not available
623 # raised if win32api not available
624 raise Abort(_('user name not available - set USERNAME '
624 raise Abort(_('user name not available - set USERNAME '
625 'environment variable'))
625 'environment variable'))
626
626
627 def username(uid=None):
627 def username(uid=None):
628 """Return the name of the user with the given uid.
628 """Return the name of the user with the given uid.
629
629
630 If uid is None, return the name of the current user."""
630 If uid is None, return the name of the current user."""
631 try:
631 try:
632 import pwd
632 import pwd
633 if uid is None:
633 if uid is None:
634 uid = os.getuid()
634 uid = os.getuid()
635 try:
635 try:
636 return pwd.getpwuid(uid)[0]
636 return pwd.getpwuid(uid)[0]
637 except KeyError:
637 except KeyError:
638 return str(uid)
638 return str(uid)
639 except ImportError:
639 except ImportError:
640 return None
640 return None
641
641
642 def groupname(gid=None):
642 def groupname(gid=None):
643 """Return the name of the group with the given gid.
643 """Return the name of the group with the given gid.
644
644
645 If gid is None, return the name of the current group."""
645 If gid is None, return the name of the current group."""
646 try:
646 try:
647 import grp
647 import grp
648 if gid is None:
648 if gid is None:
649 gid = os.getgid()
649 gid = os.getgid()
650 try:
650 try:
651 return grp.getgrgid(gid)[0]
651 return grp.getgrgid(gid)[0]
652 except KeyError:
652 except KeyError:
653 return str(gid)
653 return str(gid)
654 except ImportError:
654 except ImportError:
655 return None
655 return None
656
656
657 # File system features
657 # File system features
658
658
659 def checkfolding(path):
659 def checkfolding(path):
660 """
660 """
661 Check whether the given path is on a case-sensitive filesystem
661 Check whether the given path is on a case-sensitive filesystem
662
662
663 Requires a path (like /foo/.hg) ending with a foldable final
663 Requires a path (like /foo/.hg) ending with a foldable final
664 directory component.
664 directory component.
665 """
665 """
666 s1 = os.stat(path)
666 s1 = os.stat(path)
667 d, b = os.path.split(path)
667 d, b = os.path.split(path)
668 p2 = os.path.join(d, b.upper())
668 p2 = os.path.join(d, b.upper())
669 if path == p2:
669 if path == p2:
670 p2 = os.path.join(d, b.lower())
670 p2 = os.path.join(d, b.lower())
671 try:
671 try:
672 s2 = os.stat(p2)
672 s2 = os.stat(p2)
673 if s2 == s1:
673 if s2 == s1:
674 return False
674 return False
675 return True
675 return True
676 except:
676 except:
677 return True
677 return True
678
678
679 # Platform specific variants
679 # Platform specific variants
680 if os.name == 'nt':
680 if os.name == 'nt':
681 demandload(globals(), "msvcrt")
681 demandload(globals(), "msvcrt")
682 nulldev = 'NUL:'
682 nulldev = 'NUL:'
683
683
684 class winstdout:
684 class winstdout:
685 '''stdout on windows misbehaves if sent through a pipe'''
685 '''stdout on windows misbehaves if sent through a pipe'''
686
686
687 def __init__(self, fp):
687 def __init__(self, fp):
688 self.fp = fp
688 self.fp = fp
689
689
690 def __getattr__(self, key):
690 def __getattr__(self, key):
691 return getattr(self.fp, key)
691 return getattr(self.fp, key)
692
692
693 def close(self):
693 def close(self):
694 try:
694 try:
695 self.fp.close()
695 self.fp.close()
696 except: pass
696 except: pass
697
697
698 def write(self, s):
698 def write(self, s):
699 try:
699 try:
700 return self.fp.write(s)
700 return self.fp.write(s)
701 except IOError, inst:
701 except IOError, inst:
702 if inst.errno != 0: raise
702 if inst.errno != 0: raise
703 self.close()
703 self.close()
704 raise IOError(errno.EPIPE, 'Broken pipe')
704 raise IOError(errno.EPIPE, 'Broken pipe')
705
705
706 sys.stdout = winstdout(sys.stdout)
706 sys.stdout = winstdout(sys.stdout)
707
707
708 def system_rcpath():
708 def system_rcpath():
709 try:
709 try:
710 return system_rcpath_win32()
710 return system_rcpath_win32()
711 except:
711 except:
712 return [r'c:\mercurial\mercurial.ini']
712 return [r'c:\mercurial\mercurial.ini']
713
713
714 def os_rcpath():
714 def os_rcpath():
715 '''return default os-specific hgrc search path'''
715 '''return default os-specific hgrc search path'''
716 path = system_rcpath()
716 path = system_rcpath()
717 path.append(user_rcpath())
717 path.append(user_rcpath())
718 userprofile = os.environ.get('USERPROFILE')
718 userprofile = os.environ.get('USERPROFILE')
719 if userprofile:
719 if userprofile:
720 path.append(os.path.join(userprofile, 'mercurial.ini'))
720 path.append(os.path.join(userprofile, 'mercurial.ini'))
721 return path
721 return path
722
722
723 def user_rcpath():
723 def user_rcpath():
724 '''return os-specific hgrc search path to the user dir'''
724 '''return os-specific hgrc search path to the user dir'''
725 return os.path.join(os.path.expanduser('~'), 'mercurial.ini')
725 return os.path.join(os.path.expanduser('~'), 'mercurial.ini')
726
726
727 def parse_patch_output(output_line):
727 def parse_patch_output(output_line):
728 """parses the output produced by patch and returns the file name"""
728 """parses the output produced by patch and returns the file name"""
729 pf = output_line[14:]
729 pf = output_line[14:]
730 if pf[0] == '`':
730 if pf[0] == '`':
731 pf = pf[1:-1] # Remove the quotes
731 pf = pf[1:-1] # Remove the quotes
732 return pf
732 return pf
733
733
734 def testpid(pid):
734 def testpid(pid):
735 '''return False if pid dead, True if running or not known'''
735 '''return False if pid dead, True if running or not known'''
736 return True
736 return True
737
737
738 def is_exec(f, last):
738 def is_exec(f, last):
739 return last
739 return last
740
740
741 def set_exec(f, mode):
741 def set_exec(f, mode):
742 pass
742 pass
743
743
744 def set_binary(fd):
744 def set_binary(fd):
745 msvcrt.setmode(fd.fileno(), os.O_BINARY)
745 msvcrt.setmode(fd.fileno(), os.O_BINARY)
746
746
747 def pconvert(path):
747 def pconvert(path):
748 return path.replace("\\", "/")
748 return path.replace("\\", "/")
749
749
750 def localpath(path):
750 def localpath(path):
751 return path.replace('/', '\\')
751 return path.replace('/', '\\')
752
752
753 def normpath(path):
753 def normpath(path):
754 return pconvert(os.path.normpath(path))
754 return pconvert(os.path.normpath(path))
755
755
756 makelock = _makelock_file
756 makelock = _makelock_file
757 readlock = _readlock_file
757 readlock = _readlock_file
758
758
759 def samestat(s1, s2):
759 def samestat(s1, s2):
760 return False
760 return False
761
761
762 def shellquote(s):
762 def shellquote(s):
763 return '"%s"' % s.replace('"', '\\"')
763 return '"%s"' % s.replace('"', '\\"')
764
764
765 def explain_exit(code):
765 def explain_exit(code):
766 return _("exited with status %d") % code, code
766 return _("exited with status %d") % code, code
767
767
768 # if you change this stub into a real check, please try to implement the
768 # if you change this stub into a real check, please try to implement the
769 # username and groupname functions above, too.
769 # username and groupname functions above, too.
770 def isowner(fp, st=None):
770 def isowner(fp, st=None):
771 return True
771 return True
772
772
773 try:
773 try:
774 # override functions with win32 versions if possible
774 # override functions with win32 versions if possible
775 from util_win32 import *
775 from util_win32 import *
776 if not is_win_9x():
776 if not is_win_9x():
777 posixfile = posixfile_nt
777 posixfile = posixfile_nt
778 except ImportError:
778 except ImportError:
779 pass
779 pass
780
780
781 else:
781 else:
782 nulldev = '/dev/null'
782 nulldev = '/dev/null'
783
783
784 def rcfiles(path):
784 def rcfiles(path):
785 rcs = [os.path.join(path, 'hgrc')]
785 rcs = [os.path.join(path, 'hgrc')]
786 rcdir = os.path.join(path, 'hgrc.d')
786 rcdir = os.path.join(path, 'hgrc.d')
787 try:
787 try:
788 rcs.extend([os.path.join(rcdir, f) for f in os.listdir(rcdir)
788 rcs.extend([os.path.join(rcdir, f) for f in os.listdir(rcdir)
789 if f.endswith(".rc")])
789 if f.endswith(".rc")])
790 except OSError:
790 except OSError:
791 pass
791 pass
792 return rcs
792 return rcs
793
793
794 def os_rcpath():
794 def os_rcpath():
795 '''return default os-specific hgrc search path'''
795 '''return default os-specific hgrc search path'''
796 path = []
796 path = []
797 # old mod_python does not set sys.argv
797 # old mod_python does not set sys.argv
798 if len(getattr(sys, 'argv', [])) > 0:
798 if len(getattr(sys, 'argv', [])) > 0:
799 path.extend(rcfiles(os.path.dirname(sys.argv[0]) +
799 path.extend(rcfiles(os.path.dirname(sys.argv[0]) +
800 '/../etc/mercurial'))
800 '/../etc/mercurial'))
801 path.extend(rcfiles('/etc/mercurial'))
801 path.extend(rcfiles('/etc/mercurial'))
802 path.append(os.path.expanduser('~/.hgrc'))
802 path.append(os.path.expanduser('~/.hgrc'))
803 path = [os.path.normpath(f) for f in path]
803 path = [os.path.normpath(f) for f in path]
804 return path
804 return path
805
805
806 def parse_patch_output(output_line):
806 def parse_patch_output(output_line):
807 """parses the output produced by patch and returns the file name"""
807 """parses the output produced by patch and returns the file name"""
808 pf = output_line[14:]
808 pf = output_line[14:]
809 if pf.startswith("'") and pf.endswith("'") and " " in pf:
809 if pf.startswith("'") and pf.endswith("'") and " " in pf:
810 pf = pf[1:-1] # Remove the quotes
810 pf = pf[1:-1] # Remove the quotes
811 return pf
811 return pf
812
812
813 def is_exec(f, last):
813 def is_exec(f, last):
814 """check whether a file is executable"""
814 """check whether a file is executable"""
815 return (os.lstat(f).st_mode & 0100 != 0)
815 return (os.lstat(f).st_mode & 0100 != 0)
816
816
817 def set_exec(f, mode):
817 def set_exec(f, mode):
818 s = os.lstat(f).st_mode
818 s = os.lstat(f).st_mode
819 if (s & 0100 != 0) == mode:
819 if (s & 0100 != 0) == mode:
820 return
820 return
821 if mode:
821 if mode:
822 # Turn on +x for every +r bit when making a file executable
822 # Turn on +x for every +r bit when making a file executable
823 # and obey umask.
823 # and obey umask.
824 umask = os.umask(0)
824 umask = os.umask(0)
825 os.umask(umask)
825 os.umask(umask)
826 os.chmod(f, s | (s & 0444) >> 2 & ~umask)
826 os.chmod(f, s | (s & 0444) >> 2 & ~umask)
827 else:
827 else:
828 os.chmod(f, s & 0666)
828 os.chmod(f, s & 0666)
829
829
830 def set_binary(fd):
830 def set_binary(fd):
831 pass
831 pass
832
832
833 def pconvert(path):
833 def pconvert(path):
834 return path
834 return path
835
835
836 def localpath(path):
836 def localpath(path):
837 return path
837 return path
838
838
839 normpath = os.path.normpath
839 normpath = os.path.normpath
840 samestat = os.path.samestat
840 samestat = os.path.samestat
841
841
842 def makelock(info, pathname):
842 def makelock(info, pathname):
843 try:
843 try:
844 os.symlink(info, pathname)
844 os.symlink(info, pathname)
845 except OSError, why:
845 except OSError, why:
846 if why.errno == errno.EEXIST:
846 if why.errno == errno.EEXIST:
847 raise
847 raise
848 else:
848 else:
849 _makelock_file(info, pathname)
849 _makelock_file(info, pathname)
850
850
851 def readlock(pathname):
851 def readlock(pathname):
852 try:
852 try:
853 return os.readlink(pathname)
853 return os.readlink(pathname)
854 except OSError, why:
854 except OSError, why:
855 if why.errno == errno.EINVAL:
855 if why.errno == errno.EINVAL:
856 return _readlock_file(pathname)
856 return _readlock_file(pathname)
857 else:
857 else:
858 raise
858 raise
859
859
860 def shellquote(s):
860 def shellquote(s):
861 return "'%s'" % s.replace("'", "'\\''")
861 return "'%s'" % s.replace("'", "'\\''")
862
862
863 def testpid(pid):
863 def testpid(pid):
864 '''return False if pid dead, True if running or not sure'''
864 '''return False if pid dead, True if running or not sure'''
865 try:
865 try:
866 os.kill(pid, 0)
866 os.kill(pid, 0)
867 return True
867 return True
868 except OSError, inst:
868 except OSError, inst:
869 return inst.errno != errno.ESRCH
869 return inst.errno != errno.ESRCH
870
870
871 def explain_exit(code):
871 def explain_exit(code):
872 """return a 2-tuple (desc, code) describing a process's status"""
872 """return a 2-tuple (desc, code) describing a process's status"""
873 if os.WIFEXITED(code):
873 if os.WIFEXITED(code):
874 val = os.WEXITSTATUS(code)
874 val = os.WEXITSTATUS(code)
875 return _("exited with status %d") % val, val
875 return _("exited with status %d") % val, val
876 elif os.WIFSIGNALED(code):
876 elif os.WIFSIGNALED(code):
877 val = os.WTERMSIG(code)
877 val = os.WTERMSIG(code)
878 return _("killed by signal %d") % val, val
878 return _("killed by signal %d") % val, val
879 elif os.WIFSTOPPED(code):
879 elif os.WIFSTOPPED(code):
880 val = os.WSTOPSIG(code)
880 val = os.WSTOPSIG(code)
881 return _("stopped by signal %d") % val, val
881 return _("stopped by signal %d") % val, val
882 raise ValueError(_("invalid exit code"))
882 raise ValueError(_("invalid exit code"))
883
883
884 def isowner(fp, st=None):
884 def isowner(fp, st=None):
885 """Return True if the file object f belongs to the current user.
885 """Return True if the file object f belongs to the current user.
886
886
887 The return value of a util.fstat(f) may be passed as the st argument.
887 The return value of a util.fstat(f) may be passed as the st argument.
888 """
888 """
889 if st is None:
889 if st is None:
890 st = fstat(f)
890 st = fstat(f)
891 return st.st_uid == os.getuid()
891 return st.st_uid == os.getuid()
892
892
893
893
894 def opener(base, audit=True):
894 def opener(base, audit=True):
895 """
895 """
896 return a function that opens files relative to base
896 return a function that opens files relative to base
897
897
898 this function is used to hide the details of COW semantics and
898 this function is used to hide the details of COW semantics and
899 remote file access from higher level code.
899 remote file access from higher level code.
900 """
900 """
901 p = base
901 p = base
902 audit_p = audit
902 audit_p = audit
903
903
904 def mktempcopy(name):
904 def mktempcopy(name):
905 d, fn = os.path.split(name)
905 d, fn = os.path.split(name)
906 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
906 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
907 os.close(fd)
907 os.close(fd)
908 ofp = posixfile(temp, "wb")
908 ofp = posixfile(temp, "wb")
909 try:
909 try:
910 try:
910 try:
911 ifp = posixfile(name, "rb")
911 ifp = posixfile(name, "rb")
912 except IOError, inst:
912 except IOError, inst:
913 if not getattr(inst, 'filename', None):
913 if not getattr(inst, 'filename', None):
914 inst.filename = name
914 inst.filename = name
915 raise
915 raise
916 for chunk in filechunkiter(ifp):
916 for chunk in filechunkiter(ifp):
917 ofp.write(chunk)
917 ofp.write(chunk)
918 ifp.close()
918 ifp.close()
919 ofp.close()
919 ofp.close()
920 except:
920 except:
921 try: os.unlink(temp)
921 try: os.unlink(temp)
922 except: pass
922 except: pass
923 raise
923 raise
924 st = os.lstat(name)
924 st = os.lstat(name)
925 os.chmod(temp, st.st_mode)
925 os.chmod(temp, st.st_mode)
926 return temp
926 return temp
927
927
928 class atomictempfile(posixfile):
928 class atomictempfile(posixfile):
929 """the file will only be copied when rename is called"""
929 """the file will only be copied when rename is called"""
930 def __init__(self, name, mode):
930 def __init__(self, name, mode):
931 self.__name = name
931 self.__name = name
932 self.temp = mktempcopy(name)
932 self.temp = mktempcopy(name)
933 posixfile.__init__(self, self.temp, mode)
933 posixfile.__init__(self, self.temp, mode)
934 def rename(self):
934 def rename(self):
935 if not self.closed:
935 if not self.closed:
936 posixfile.close(self)
936 posixfile.close(self)
937 rename(self.temp, localpath(self.__name))
937 rename(self.temp, localpath(self.__name))
938 def __del__(self):
938 def __del__(self):
939 if not self.closed:
939 if not self.closed:
940 try:
940 try:
941 os.unlink(self.temp)
941 os.unlink(self.temp)
942 except: pass
942 except: pass
943 posixfile.close(self)
943 posixfile.close(self)
944
944
945 class atomicfile(atomictempfile):
945 class atomicfile(atomictempfile):
946 """the file will only be copied on close"""
946 """the file will only be copied on close"""
947 def __init__(self, name, mode):
947 def __init__(self, name, mode):
948 atomictempfile.__init__(self, name, mode)
948 atomictempfile.__init__(self, name, mode)
949 def close(self):
949 def close(self):
950 self.rename()
950 self.rename()
951 def __del__(self):
951 def __del__(self):
952 self.rename()
952 self.rename()
953
953
954 def o(path, mode="r", text=False, atomic=False, atomictemp=False):
954 def o(path, mode="r", text=False, atomic=False, atomictemp=False):
955 if audit_p:
955 if audit_p:
956 audit_path(path)
956 audit_path(path)
957 f = os.path.join(p, path)
957 f = os.path.join(p, path)
958
958
959 if not text:
959 if not text:
960 mode += "b" # for that other OS
960 mode += "b" # for that other OS
961
961
962 if mode[0] != "r":
962 if mode[0] != "r":
963 try:
963 try:
964 nlink = nlinks(f)
964 nlink = nlinks(f)
965 except OSError:
965 except OSError:
966 d = os.path.dirname(f)
966 d = os.path.dirname(f)
967 if not os.path.isdir(d):
967 if not os.path.isdir(d):
968 os.makedirs(d)
968 os.makedirs(d)
969 else:
969 else:
970 if atomic:
970 if atomic:
971 return atomicfile(f, mode)
971 return atomicfile(f, mode)
972 elif atomictemp:
972 elif atomictemp:
973 return atomictempfile(f, mode)
973 return atomictempfile(f, mode)
974 if nlink > 1:
974 if nlink > 1:
975 rename(mktempcopy(f), f)
975 rename(mktempcopy(f), f)
976 return posixfile(f, mode)
976 return posixfile(f, mode)
977
977
978 return o
978 return o
979
979
980 class chunkbuffer(object):
980 class chunkbuffer(object):
981 """Allow arbitrary sized chunks of data to be efficiently read from an
981 """Allow arbitrary sized chunks of data to be efficiently read from an
982 iterator over chunks of arbitrary size."""
982 iterator over chunks of arbitrary size."""
983
983
984 def __init__(self, in_iter, targetsize = 2**16):
984 def __init__(self, in_iter, targetsize = 2**16):
985 """in_iter is the iterator that's iterating over the input chunks.
985 """in_iter is the iterator that's iterating over the input chunks.
986 targetsize is how big a buffer to try to maintain."""
986 targetsize is how big a buffer to try to maintain."""
987 self.in_iter = iter(in_iter)
987 self.in_iter = iter(in_iter)
988 self.buf = ''
988 self.buf = ''
989 self.targetsize = int(targetsize)
989 self.targetsize = int(targetsize)
990 if self.targetsize <= 0:
990 if self.targetsize <= 0:
991 raise ValueError(_("targetsize must be greater than 0, was %d") %
991 raise ValueError(_("targetsize must be greater than 0, was %d") %
992 targetsize)
992 targetsize)
993 self.iterempty = False
993 self.iterempty = False
994
994
995 def fillbuf(self):
995 def fillbuf(self):
996 """Ignore target size; read every chunk from iterator until empty."""
996 """Ignore target size; read every chunk from iterator until empty."""
997 if not self.iterempty:
997 if not self.iterempty:
998 collector = cStringIO.StringIO()
998 collector = cStringIO.StringIO()
999 collector.write(self.buf)
999 collector.write(self.buf)
1000 for ch in self.in_iter:
1000 for ch in self.in_iter:
1001 collector.write(ch)
1001 collector.write(ch)
1002 self.buf = collector.getvalue()
1002 self.buf = collector.getvalue()
1003 self.iterempty = True
1003 self.iterempty = True
1004
1004
1005 def read(self, l):
1005 def read(self, l):
1006 """Read L bytes of data from the iterator of chunks of data.
1006 """Read L bytes of data from the iterator of chunks of data.
1007 Returns less than L bytes if the iterator runs dry."""
1007 Returns less than L bytes if the iterator runs dry."""
1008 if l > len(self.buf) and not self.iterempty:
1008 if l > len(self.buf) and not self.iterempty:
1009 # Clamp to a multiple of self.targetsize
1009 # Clamp to a multiple of self.targetsize
1010 targetsize = self.targetsize * ((l // self.targetsize) + 1)
1010 targetsize = self.targetsize * ((l // self.targetsize) + 1)
1011 collector = cStringIO.StringIO()
1011 collector = cStringIO.StringIO()
1012 collector.write(self.buf)
1012 collector.write(self.buf)
1013 collected = len(self.buf)
1013 collected = len(self.buf)
1014 for chunk in self.in_iter:
1014 for chunk in self.in_iter:
1015 collector.write(chunk)
1015 collector.write(chunk)
1016 collected += len(chunk)
1016 collected += len(chunk)
1017 if collected >= targetsize:
1017 if collected >= targetsize:
1018 break
1018 break
1019 if collected < targetsize:
1019 if collected < targetsize:
1020 self.iterempty = True
1020 self.iterempty = True
1021 self.buf = collector.getvalue()
1021 self.buf = collector.getvalue()
1022 s, self.buf = self.buf[:l], buffer(self.buf, l)
1022 s, self.buf = self.buf[:l], buffer(self.buf, l)
1023 return s
1023 return s
1024
1024
1025 def filechunkiter(f, size=65536, limit=None):
1025 def filechunkiter(f, size=65536, limit=None):
1026 """Create a generator that produces the data in the file size
1026 """Create a generator that produces the data in the file size
1027 (default 65536) bytes at a time, up to optional limit (default is
1027 (default 65536) bytes at a time, up to optional limit (default is
1028 to read all data). Chunks may be less than size bytes if the
1028 to read all data). Chunks may be less than size bytes if the
1029 chunk is the last chunk in the file, or the file is a socket or
1029 chunk is the last chunk in the file, or the file is a socket or
1030 some other type of file that sometimes reads less data than is
1030 some other type of file that sometimes reads less data than is
1031 requested."""
1031 requested."""
1032 assert size >= 0
1032 assert size >= 0
1033 assert limit is None or limit >= 0
1033 assert limit is None or limit >= 0
1034 while True:
1034 while True:
1035 if limit is None: nbytes = size
1035 if limit is None: nbytes = size
1036 else: nbytes = min(limit, size)
1036 else: nbytes = min(limit, size)
1037 s = nbytes and f.read(nbytes)
1037 s = nbytes and f.read(nbytes)
1038 if not s: break
1038 if not s: break
1039 if limit: limit -= len(s)
1039 if limit: limit -= len(s)
1040 yield s
1040 yield s
1041
1041
1042 def makedate():
1042 def makedate():
1043 lt = time.localtime()
1043 lt = time.localtime()
1044 if lt[8] == 1 and time.daylight:
1044 if lt[8] == 1 and time.daylight:
1045 tz = time.altzone
1045 tz = time.altzone
1046 else:
1046 else:
1047 tz = time.timezone
1047 tz = time.timezone
1048 return time.mktime(lt), tz
1048 return time.mktime(lt), tz
1049
1049
1050 def datestr(date=None, format='%a %b %d %H:%M:%S %Y', timezone=True):
1050 def datestr(date=None, format='%a %b %d %H:%M:%S %Y', timezone=True):
1051 """represent a (unixtime, offset) tuple as a localized time.
1051 """represent a (unixtime, offset) tuple as a localized time.
1052 unixtime is seconds since the epoch, and offset is the time zone's
1052 unixtime is seconds since the epoch, and offset is the time zone's
1053 number of seconds away from UTC. if timezone is false, do not
1053 number of seconds away from UTC. if timezone is false, do not
1054 append time zone to string."""
1054 append time zone to string."""
1055 t, tz = date or makedate()
1055 t, tz = date or makedate()
1056 s = time.strftime(format, time.gmtime(float(t) - tz))
1056 s = time.strftime(format, time.gmtime(float(t) - tz))
1057 if timezone:
1057 if timezone:
1058 s += " %+03d%02d" % (-tz / 3600, ((-tz % 3600) / 60))
1058 s += " %+03d%02d" % (-tz / 3600, ((-tz % 3600) / 60))
1059 return s
1059 return s
1060
1060
1061 def strdate(string, format='%a %b %d %H:%M:%S %Y'):
1061 def strdate(string, format='%a %b %d %H:%M:%S %Y'):
1062 """parse a localized time string and return a (unixtime, offset) tuple.
1062 """parse a localized time string and return a (unixtime, offset) tuple.
1063 if the string cannot be parsed, ValueError is raised."""
1063 if the string cannot be parsed, ValueError is raised."""
1064 def hastimezone(string):
1064 def timezone(string):
1065 return (string[-4:].isdigit() and
1065 tz = string.split()[-1]
1066 (string[-5] == '+' or string[-5] == '-') and
1066 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
1067 string[-6].isspace())
1067 tz = int(tz)
1068 offset = - 3600 * (tz / 100) - 60 * (tz % 100)
1069 return offset
1070 if tz == "GMT" or tz == "UTC":
1071 return 0
1072 return None
1068
1073
1069 # NOTE: unixtime = localunixtime + offset
1074 # NOTE: unixtime = localunixtime + offset
1070 if hastimezone(string):
1075 offset, date = timezone(string), string
1071 date, tz = string[:-6], string[-5:]
1076 if offset != None:
1072 tz = int(tz)
1077 date = " ".join(string.split()[:-1])
1073 offset = - 3600 * (tz / 100) - 60 * (tz % 100)
1074 else:
1075 date, offset = string, None
1076
1078
1077 # add missing elements
1079 # add missing elements
1078 if '%y' not in format.lower():
1080 if '%y' not in format.lower():
1079 date += "@" + datestr(makedate(), "%Y", False)
1081 date += "@" + datestr(makedate(), "%Y", False)
1080 format += "@%Y"
1082 format += "@%Y"
1081 if '%m' not in format and '%b' not in format:
1083 if '%m' not in format and '%b' not in format:
1082 date += "@" + datestr(makedate(), "%m", False)
1084 date += "@" + datestr(makedate(), "%m", False)
1083 format += "@%m"
1085 format += "@%m"
1084 if '%d' not in format:
1086 if '%d' not in format:
1085 date += "@" + datestr(makedate(), "%d", False)
1087 date += "@" + datestr(makedate(), "%d", False)
1086 format += "@%d"
1088 format += "@%d"
1087
1089
1088 timetuple = time.strptime(date, format)
1090 timetuple = time.strptime(date, format)
1089 localunixtime = int(calendar.timegm(timetuple))
1091 localunixtime = int(calendar.timegm(timetuple))
1090 if offset is None:
1092 if offset is None:
1091 # local timezone
1093 # local timezone
1092 unixtime = int(time.mktime(timetuple))
1094 unixtime = int(time.mktime(timetuple))
1093 offset = unixtime - localunixtime
1095 offset = unixtime - localunixtime
1094 else:
1096 else:
1095 unixtime = localunixtime + offset
1097 unixtime = localunixtime + offset
1096 return unixtime, offset
1098 return unixtime, offset
1097
1099
1098 def parsedate(string, formats=None):
1100 def parsedate(string, formats=None):
1099 """parse a localized time string and return a (unixtime, offset) tuple.
1101 """parse a localized time string and return a (unixtime, offset) tuple.
1100 The date may be a "unixtime offset" string or in one of the specified
1102 The date may be a "unixtime offset" string or in one of the specified
1101 formats."""
1103 formats."""
1102 if not string:
1104 if not string:
1103 return 0, 0
1105 return 0, 0
1104 if not formats:
1106 if not formats:
1105 formats = defaultdateformats
1107 formats = defaultdateformats
1106 string = string.strip()
1108 string = string.strip()
1107 try:
1109 try:
1108 when, offset = map(int, string.split(' '))
1110 when, offset = map(int, string.split(' '))
1109 except ValueError:
1111 except ValueError:
1110 for format in formats:
1112 for format in formats:
1111 try:
1113 try:
1112 when, offset = strdate(string, format)
1114 when, offset = strdate(string, format)
1113 except ValueError:
1115 except ValueError:
1114 pass
1116 pass
1115 else:
1117 else:
1116 break
1118 break
1117 else:
1119 else:
1118 raise Abort(_('invalid date: %r ') % string)
1120 raise Abort(_('invalid date: %r ') % string)
1119 # validate explicit (probably user-specified) date and
1121 # validate explicit (probably user-specified) date and
1120 # time zone offset. values must fit in signed 32 bits for
1122 # time zone offset. values must fit in signed 32 bits for
1121 # current 32-bit linux runtimes. timezones go from UTC-12
1123 # current 32-bit linux runtimes. timezones go from UTC-12
1122 # to UTC+14
1124 # to UTC+14
1123 if abs(when) > 0x7fffffff:
1125 if abs(when) > 0x7fffffff:
1124 raise Abort(_('date exceeds 32 bits: %d') % when)
1126 raise Abort(_('date exceeds 32 bits: %d') % when)
1125 if offset < -50400 or offset > 43200:
1127 if offset < -50400 or offset > 43200:
1126 raise Abort(_('impossible time zone offset: %d') % offset)
1128 raise Abort(_('impossible time zone offset: %d') % offset)
1127 return when, offset
1129 return when, offset
1128
1130
1129 def shortuser(user):
1131 def shortuser(user):
1130 """Return a short representation of a user name or email address."""
1132 """Return a short representation of a user name or email address."""
1131 f = user.find('@')
1133 f = user.find('@')
1132 if f >= 0:
1134 if f >= 0:
1133 user = user[:f]
1135 user = user[:f]
1134 f = user.find('<')
1136 f = user.find('<')
1135 if f >= 0:
1137 if f >= 0:
1136 user = user[f+1:]
1138 user = user[f+1:]
1137 f = user.find(' ')
1139 f = user.find(' ')
1138 if f >= 0:
1140 if f >= 0:
1139 user = user[:f]
1141 user = user[:f]
1140 f = user.find('.')
1142 f = user.find('.')
1141 if f >= 0:
1143 if f >= 0:
1142 user = user[:f]
1144 user = user[:f]
1143 return user
1145 return user
1144
1146
1145 def ellipsis(text, maxlength=400):
1147 def ellipsis(text, maxlength=400):
1146 """Trim string to at most maxlength (default: 400) characters."""
1148 """Trim string to at most maxlength (default: 400) characters."""
1147 if len(text) <= maxlength:
1149 if len(text) <= maxlength:
1148 return text
1150 return text
1149 else:
1151 else:
1150 return "%s..." % (text[:maxlength-3])
1152 return "%s..." % (text[:maxlength-3])
1151
1153
1152 def walkrepos(path):
1154 def walkrepos(path):
1153 '''yield every hg repository under path, recursively.'''
1155 '''yield every hg repository under path, recursively.'''
1154 def errhandler(err):
1156 def errhandler(err):
1155 if err.filename == path:
1157 if err.filename == path:
1156 raise err
1158 raise err
1157
1159
1158 for root, dirs, files in os.walk(path, onerror=errhandler):
1160 for root, dirs, files in os.walk(path, onerror=errhandler):
1159 for d in dirs:
1161 for d in dirs:
1160 if d == '.hg':
1162 if d == '.hg':
1161 yield root
1163 yield root
1162 dirs[:] = []
1164 dirs[:] = []
1163 break
1165 break
1164
1166
1165 _rcpath = None
1167 _rcpath = None
1166
1168
1167 def rcpath():
1169 def rcpath():
1168 '''return hgrc search path. if env var HGRCPATH is set, use it.
1170 '''return hgrc search path. if env var HGRCPATH is set, use it.
1169 for each item in path, if directory, use files ending in .rc,
1171 for each item in path, if directory, use files ending in .rc,
1170 else use item.
1172 else use item.
1171 make HGRCPATH empty to only look in .hg/hgrc of current repo.
1173 make HGRCPATH empty to only look in .hg/hgrc of current repo.
1172 if no HGRCPATH, use default os-specific path.'''
1174 if no HGRCPATH, use default os-specific path.'''
1173 global _rcpath
1175 global _rcpath
1174 if _rcpath is None:
1176 if _rcpath is None:
1175 if 'HGRCPATH' in os.environ:
1177 if 'HGRCPATH' in os.environ:
1176 _rcpath = []
1178 _rcpath = []
1177 for p in os.environ['HGRCPATH'].split(os.pathsep):
1179 for p in os.environ['HGRCPATH'].split(os.pathsep):
1178 if not p: continue
1180 if not p: continue
1179 if os.path.isdir(p):
1181 if os.path.isdir(p):
1180 for f in os.listdir(p):
1182 for f in os.listdir(p):
1181 if f.endswith('.rc'):
1183 if f.endswith('.rc'):
1182 _rcpath.append(os.path.join(p, f))
1184 _rcpath.append(os.path.join(p, f))
1183 else:
1185 else:
1184 _rcpath.append(p)
1186 _rcpath.append(p)
1185 else:
1187 else:
1186 _rcpath = os_rcpath()
1188 _rcpath = os_rcpath()
1187 return _rcpath
1189 return _rcpath
1188
1190
1189 def bytecount(nbytes):
1191 def bytecount(nbytes):
1190 '''return byte count formatted as readable string, with units'''
1192 '''return byte count formatted as readable string, with units'''
1191
1193
1192 units = (
1194 units = (
1193 (100, 1<<30, _('%.0f GB')),
1195 (100, 1<<30, _('%.0f GB')),
1194 (10, 1<<30, _('%.1f GB')),
1196 (10, 1<<30, _('%.1f GB')),
1195 (1, 1<<30, _('%.2f GB')),
1197 (1, 1<<30, _('%.2f GB')),
1196 (100, 1<<20, _('%.0f MB')),
1198 (100, 1<<20, _('%.0f MB')),
1197 (10, 1<<20, _('%.1f MB')),
1199 (10, 1<<20, _('%.1f MB')),
1198 (1, 1<<20, _('%.2f MB')),
1200 (1, 1<<20, _('%.2f MB')),
1199 (100, 1<<10, _('%.0f KB')),
1201 (100, 1<<10, _('%.0f KB')),
1200 (10, 1<<10, _('%.1f KB')),
1202 (10, 1<<10, _('%.1f KB')),
1201 (1, 1<<10, _('%.2f KB')),
1203 (1, 1<<10, _('%.2f KB')),
1202 (1, 1, _('%.0f bytes')),
1204 (1, 1, _('%.0f bytes')),
1203 )
1205 )
1204
1206
1205 for multiplier, divisor, format in units:
1207 for multiplier, divisor, format in units:
1206 if nbytes >= divisor * multiplier:
1208 if nbytes >= divisor * multiplier:
1207 return format % (nbytes / float(divisor))
1209 return format % (nbytes / float(divisor))
1208 return units[-1][2] % nbytes
1210 return units[-1][2] % nbytes
1209
1211
1210 def drop_scheme(scheme, path):
1212 def drop_scheme(scheme, path):
1211 sc = scheme + ':'
1213 sc = scheme + ':'
1212 if path.startswith(sc):
1214 if path.startswith(sc):
1213 path = path[len(sc):]
1215 path = path[len(sc):]
1214 if path.startswith('//'):
1216 if path.startswith('//'):
1215 path = path[2:]
1217 path = path[2:]
1216 return path
1218 return path
General Comments 0
You need to be logged in to leave comments. Login now