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