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