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