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