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