##// END OF EJS Templates
Make it possible to use the root directory as the root of a repository.
Manpreet Singh -
r2271:90b12273 default
parent child Browse files
Show More
@@ -1,890 +1,892 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):
209 rootsep = root
208 else:
210 else:
209 rootsep = root + os.sep
211 rootsep = root + os.sep
210 name = myname
212 name = myname
211 if not os.path.isabs(name):
213 if not os.path.isabs(name):
212 name = os.path.join(root, cwd, name)
214 name = os.path.join(root, cwd, name)
213 name = os.path.normpath(name)
215 name = os.path.normpath(name)
214 if name.startswith(rootsep):
216 if name.startswith(rootsep):
215 name = name[len(rootsep):]
217 name = name[len(rootsep):]
216 audit_path(name)
218 audit_path(name)
217 return pconvert(name)
219 return pconvert(name)
218 elif name == root:
220 elif name == root:
219 return ''
221 return ''
220 else:
222 else:
221 # Determine whether `name' is in the hierarchy at or beneath `root',
223 # Determine whether `name' is in the hierarchy at or beneath `root',
222 # 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
223 # check name == '/', because that doesn't work on windows). For each
225 # check name == '/', because that doesn't work on windows). For each
224 # `name', compare dev/inode numbers. If they match, the list `rel'
226 # `name', compare dev/inode numbers. If they match, the list `rel'
225 # holds the reversed list of components making up the relative file
227 # holds the reversed list of components making up the relative file
226 # name we want.
228 # name we want.
227 root_st = os.stat(root)
229 root_st = os.stat(root)
228 rel = []
230 rel = []
229 while True:
231 while True:
230 try:
232 try:
231 name_st = os.stat(name)
233 name_st = os.stat(name)
232 except OSError:
234 except OSError:
233 break
235 break
234 if samestat(name_st, root_st):
236 if samestat(name_st, root_st):
235 rel.reverse()
237 rel.reverse()
236 name = os.path.join(*rel)
238 name = os.path.join(*rel)
237 audit_path(name)
239 audit_path(name)
238 return pconvert(name)
240 return pconvert(name)
239 dirname, basename = os.path.split(name)
241 dirname, basename = os.path.split(name)
240 rel.append(basename)
242 rel.append(basename)
241 if dirname == name:
243 if dirname == name:
242 break
244 break
243 name = dirname
245 name = dirname
244
246
245 raise Abort('%s not under root' % myname)
247 raise Abort('%s not under root' % myname)
246
248
247 def matcher(canonroot, cwd='', names=['.'], inc=[], exc=[], head='', src=None):
249 def matcher(canonroot, cwd='', names=['.'], inc=[], exc=[], head='', src=None):
248 return _matcher(canonroot, cwd, names, inc, exc, head, 'glob', src)
250 return _matcher(canonroot, cwd, names, inc, exc, head, 'glob', src)
249
251
250 def cmdmatcher(canonroot, cwd='', names=['.'], inc=[], exc=[], head='', src=None):
252 def cmdmatcher(canonroot, cwd='', names=['.'], inc=[], exc=[], head='', src=None):
251 if os.name == 'nt':
253 if os.name == 'nt':
252 dflt_pat = 'glob'
254 dflt_pat = 'glob'
253 else:
255 else:
254 dflt_pat = 'relpath'
256 dflt_pat = 'relpath'
255 return _matcher(canonroot, cwd, names, inc, exc, head, dflt_pat, src)
257 return _matcher(canonroot, cwd, names, inc, exc, head, dflt_pat, src)
256
258
257 def _matcher(canonroot, cwd, names, inc, exc, head, dflt_pat, src):
259 def _matcher(canonroot, cwd, names, inc, exc, head, dflt_pat, src):
258 """build a function to match a set of file patterns
260 """build a function to match a set of file patterns
259
261
260 arguments:
262 arguments:
261 canonroot - the canonical root of the tree you're matching against
263 canonroot - the canonical root of the tree you're matching against
262 cwd - the current working directory, if relevant
264 cwd - the current working directory, if relevant
263 names - patterns to find
265 names - patterns to find
264 inc - patterns to include
266 inc - patterns to include
265 exc - patterns to exclude
267 exc - patterns to exclude
266 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
267
269
268 a pattern is one of:
270 a pattern is one of:
269 'glob:<rooted glob>'
271 'glob:<rooted glob>'
270 're:<rooted regexp>'
272 're:<rooted regexp>'
271 'path:<rooted path>'
273 'path:<rooted path>'
272 'relglob:<relative glob>'
274 'relglob:<relative glob>'
273 'relpath:<relative path>'
275 'relpath:<relative path>'
274 'relre:<relative regexp>'
276 'relre:<relative regexp>'
275 '<rooted path or regexp>'
277 '<rooted path or regexp>'
276
278
277 returns:
279 returns:
278 a 3-tuple containing
280 a 3-tuple containing
279 - list of explicit non-pattern names passed in
281 - list of explicit non-pattern names passed in
280 - a bool match(filename) function
282 - a bool match(filename) function
281 - a bool indicating if any patterns were passed in
283 - a bool indicating if any patterns were passed in
282
284
283 todo:
285 todo:
284 make head regex a rooted bool
286 make head regex a rooted bool
285 """
287 """
286
288
287 def contains_glob(name):
289 def contains_glob(name):
288 for c in name:
290 for c in name:
289 if c in _globchars: return True
291 if c in _globchars: return True
290 return False
292 return False
291
293
292 def regex(kind, name, tail):
294 def regex(kind, name, tail):
293 '''convert a pattern into a regular expression'''
295 '''convert a pattern into a regular expression'''
294 if kind == 're':
296 if kind == 're':
295 return name
297 return name
296 elif kind == 'path':
298 elif kind == 'path':
297 return '^' + re.escape(name) + '(?:/|$)'
299 return '^' + re.escape(name) + '(?:/|$)'
298 elif kind == 'relglob':
300 elif kind == 'relglob':
299 return head + globre(name, '(?:|.*/)', tail)
301 return head + globre(name, '(?:|.*/)', tail)
300 elif kind == 'relpath':
302 elif kind == 'relpath':
301 return head + re.escape(name) + tail
303 return head + re.escape(name) + tail
302 elif kind == 'relre':
304 elif kind == 'relre':
303 if name.startswith('^'):
305 if name.startswith('^'):
304 return name
306 return name
305 return '.*' + name
307 return '.*' + name
306 return head + globre(name, '', tail)
308 return head + globre(name, '', tail)
307
309
308 def matchfn(pats, tail):
310 def matchfn(pats, tail):
309 """build a matching function from a set of patterns"""
311 """build a matching function from a set of patterns"""
310 if not pats:
312 if not pats:
311 return
313 return
312 matches = []
314 matches = []
313 for k, p in pats:
315 for k, p in pats:
314 try:
316 try:
315 pat = '(?:%s)' % regex(k, p, tail)
317 pat = '(?:%s)' % regex(k, p, tail)
316 matches.append(re.compile(pat).match)
318 matches.append(re.compile(pat).match)
317 except re.error:
319 except re.error:
318 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))
319 else: raise Abort("invalid pattern (%s): %s" % (k, p))
321 else: raise Abort("invalid pattern (%s): %s" % (k, p))
320
322
321 def buildfn(text):
323 def buildfn(text):
322 for m in matches:
324 for m in matches:
323 r = m(text)
325 r = m(text)
324 if r:
326 if r:
325 return r
327 return r
326
328
327 return buildfn
329 return buildfn
328
330
329 def globprefix(pat):
331 def globprefix(pat):
330 '''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'''
331 root = []
333 root = []
332 for p in pat.split(os.sep):
334 for p in pat.split(os.sep):
333 if contains_glob(p): break
335 if contains_glob(p): break
334 root.append(p)
336 root.append(p)
335 return '/'.join(root)
337 return '/'.join(root)
336
338
337 pats = []
339 pats = []
338 files = []
340 files = []
339 roots = []
341 roots = []
340 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]:
341 if kind in ('glob', 'relpath'):
343 if kind in ('glob', 'relpath'):
342 name = canonpath(canonroot, cwd, name)
344 name = canonpath(canonroot, cwd, name)
343 if name == '':
345 if name == '':
344 kind, name = 'glob', '**'
346 kind, name = 'glob', '**'
345 if kind in ('glob', 'path', 're'):
347 if kind in ('glob', 'path', 're'):
346 pats.append((kind, name))
348 pats.append((kind, name))
347 if kind == 'glob':
349 if kind == 'glob':
348 root = globprefix(name)
350 root = globprefix(name)
349 if root: roots.append(root)
351 if root: roots.append(root)
350 elif kind == 'relpath':
352 elif kind == 'relpath':
351 files.append((kind, name))
353 files.append((kind, name))
352 roots.append(name)
354 roots.append(name)
353
355
354 patmatch = matchfn(pats, '$') or always
356 patmatch = matchfn(pats, '$') or always
355 filematch = matchfn(files, '(?:/|$)') or always
357 filematch = matchfn(files, '(?:/|$)') or always
356 incmatch = always
358 incmatch = always
357 if inc:
359 if inc:
358 incmatch = matchfn(map(patkind, inc), '(?:/|$)')
360 incmatch = matchfn(map(patkind, inc), '(?:/|$)')
359 excmatch = lambda fn: False
361 excmatch = lambda fn: False
360 if exc:
362 if exc:
361 excmatch = matchfn(map(patkind, exc), '(?:/|$)')
363 excmatch = matchfn(map(patkind, exc), '(?:/|$)')
362
364
363 return (roots,
365 return (roots,
364 lambda fn: (incmatch(fn) and not excmatch(fn) and
366 lambda fn: (incmatch(fn) and not excmatch(fn) and
365 (fn.endswith('/') or
367 (fn.endswith('/') or
366 (not pats and not files) or
368 (not pats and not files) or
367 (pats and patmatch(fn)) or
369 (pats and patmatch(fn)) or
368 (files and filematch(fn)))),
370 (files and filematch(fn)))),
369 (inc or exc or (pats and pats != [('glob', '**')])) and True)
371 (inc or exc or (pats and pats != [('glob', '**')])) and True)
370
372
371 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None):
373 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None):
372 '''enhanced shell command execution.
374 '''enhanced shell command execution.
373 run with environment maybe modified, maybe in different dir.
375 run with environment maybe modified, maybe in different dir.
374
376
375 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,
376 print error message and return status, else raise onerr object as
378 print error message and return status, else raise onerr object as
377 exception.'''
379 exception.'''
378 oldenv = {}
380 oldenv = {}
379 for k in environ:
381 for k in environ:
380 oldenv[k] = os.environ.get(k)
382 oldenv[k] = os.environ.get(k)
381 if cwd is not None:
383 if cwd is not None:
382 oldcwd = os.getcwd()
384 oldcwd = os.getcwd()
383 try:
385 try:
384 for k, v in environ.iteritems():
386 for k, v in environ.iteritems():
385 os.environ[k] = str(v)
387 os.environ[k] = str(v)
386 if cwd is not None and oldcwd != cwd:
388 if cwd is not None and oldcwd != cwd:
387 os.chdir(cwd)
389 os.chdir(cwd)
388 rc = os.system(cmd)
390 rc = os.system(cmd)
389 if rc and onerr:
391 if rc and onerr:
390 errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]),
392 errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]),
391 explain_exit(rc)[0])
393 explain_exit(rc)[0])
392 if errprefix:
394 if errprefix:
393 errmsg = '%s: %s' % (errprefix, errmsg)
395 errmsg = '%s: %s' % (errprefix, errmsg)
394 try:
396 try:
395 onerr.warn(errmsg + '\n')
397 onerr.warn(errmsg + '\n')
396 except AttributeError:
398 except AttributeError:
397 raise onerr(errmsg)
399 raise onerr(errmsg)
398 return rc
400 return rc
399 finally:
401 finally:
400 for k, v in oldenv.iteritems():
402 for k, v in oldenv.iteritems():
401 if v is None:
403 if v is None:
402 del os.environ[k]
404 del os.environ[k]
403 else:
405 else:
404 os.environ[k] = v
406 os.environ[k] = v
405 if cwd is not None and oldcwd != cwd:
407 if cwd is not None and oldcwd != cwd:
406 os.chdir(oldcwd)
408 os.chdir(oldcwd)
407
409
408 def rename(src, dst):
410 def rename(src, dst):
409 """forcibly rename a file"""
411 """forcibly rename a file"""
410 try:
412 try:
411 os.rename(src, dst)
413 os.rename(src, dst)
412 except OSError, err:
414 except OSError, err:
413 # on windows, rename to existing file is not allowed, so we
415 # on windows, rename to existing file is not allowed, so we
414 # must delete destination first. but if file is open, unlink
416 # must delete destination first. but if file is open, unlink
415 # schedules it for delete but does not delete it. rename
417 # schedules it for delete but does not delete it. rename
416 # happens immediately even for open files, so we create
418 # happens immediately even for open files, so we create
417 # temporary file, delete it, rename destination to that name,
419 # temporary file, delete it, rename destination to that name,
418 # then delete that. then rename is safe to do.
420 # then delete that. then rename is safe to do.
419 fd, temp = tempfile.mkstemp(dir=os.path.dirname(dst) or '.')
421 fd, temp = tempfile.mkstemp(dir=os.path.dirname(dst) or '.')
420 os.close(fd)
422 os.close(fd)
421 os.unlink(temp)
423 os.unlink(temp)
422 os.rename(dst, temp)
424 os.rename(dst, temp)
423 os.unlink(temp)
425 os.unlink(temp)
424 os.rename(src, dst)
426 os.rename(src, dst)
425
427
426 def unlink(f):
428 def unlink(f):
427 """unlink and remove the directory if it is empty"""
429 """unlink and remove the directory if it is empty"""
428 os.unlink(f)
430 os.unlink(f)
429 # try removing directories that might now be empty
431 # try removing directories that might now be empty
430 try:
432 try:
431 os.removedirs(os.path.dirname(f))
433 os.removedirs(os.path.dirname(f))
432 except OSError:
434 except OSError:
433 pass
435 pass
434
436
435 def copyfiles(src, dst, hardlink=None):
437 def copyfiles(src, dst, hardlink=None):
436 """Copy a directory tree using hardlinks if possible"""
438 """Copy a directory tree using hardlinks if possible"""
437
439
438 if hardlink is None:
440 if hardlink is None:
439 hardlink = (os.stat(src).st_dev ==
441 hardlink = (os.stat(src).st_dev ==
440 os.stat(os.path.dirname(dst)).st_dev)
442 os.stat(os.path.dirname(dst)).st_dev)
441
443
442 if os.path.isdir(src):
444 if os.path.isdir(src):
443 os.mkdir(dst)
445 os.mkdir(dst)
444 for name in os.listdir(src):
446 for name in os.listdir(src):
445 srcname = os.path.join(src, name)
447 srcname = os.path.join(src, name)
446 dstname = os.path.join(dst, name)
448 dstname = os.path.join(dst, name)
447 copyfiles(srcname, dstname, hardlink)
449 copyfiles(srcname, dstname, hardlink)
448 else:
450 else:
449 if hardlink:
451 if hardlink:
450 try:
452 try:
451 os_link(src, dst)
453 os_link(src, dst)
452 except (IOError, OSError):
454 except (IOError, OSError):
453 hardlink = False
455 hardlink = False
454 shutil.copy(src, dst)
456 shutil.copy(src, dst)
455 else:
457 else:
456 shutil.copy(src, dst)
458 shutil.copy(src, dst)
457
459
458 def audit_path(path):
460 def audit_path(path):
459 """Abort if path contains dangerous components"""
461 """Abort if path contains dangerous components"""
460 parts = os.path.normcase(path).split(os.sep)
462 parts = os.path.normcase(path).split(os.sep)
461 if (os.path.splitdrive(path)[0] or parts[0] in ('.hg', '')
463 if (os.path.splitdrive(path)[0] or parts[0] in ('.hg', '')
462 or os.pardir in parts):
464 or os.pardir in parts):
463 raise Abort(_("path contains illegal component: %s\n") % path)
465 raise Abort(_("path contains illegal component: %s\n") % path)
464
466
465 def _makelock_file(info, pathname):
467 def _makelock_file(info, pathname):
466 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)
467 os.write(ld, info)
469 os.write(ld, info)
468 os.close(ld)
470 os.close(ld)
469
471
470 def _readlock_file(pathname):
472 def _readlock_file(pathname):
471 return posixfile(pathname).read()
473 return posixfile(pathname).read()
472
474
473 def nlinks(pathname):
475 def nlinks(pathname):
474 """Return number of hardlinks for the given file."""
476 """Return number of hardlinks for the given file."""
475 return os.stat(pathname).st_nlink
477 return os.stat(pathname).st_nlink
476
478
477 if hasattr(os, 'link'):
479 if hasattr(os, 'link'):
478 os_link = os.link
480 os_link = os.link
479 else:
481 else:
480 def os_link(src, dst):
482 def os_link(src, dst):
481 raise OSError(0, _("Hardlinks not supported"))
483 raise OSError(0, _("Hardlinks not supported"))
482
484
483 def fstat(fp):
485 def fstat(fp):
484 '''stat file object that may not have fileno method.'''
486 '''stat file object that may not have fileno method.'''
485 try:
487 try:
486 return os.fstat(fp.fileno())
488 return os.fstat(fp.fileno())
487 except AttributeError:
489 except AttributeError:
488 return os.stat(fp.name)
490 return os.stat(fp.name)
489
491
490 posixfile = file
492 posixfile = file
491
493
492 def is_win_9x():
494 def is_win_9x():
493 '''return true if run on windows 95, 98 or me.'''
495 '''return true if run on windows 95, 98 or me.'''
494 try:
496 try:
495 return sys.getwindowsversion()[3] == 1
497 return sys.getwindowsversion()[3] == 1
496 except AttributeError:
498 except AttributeError:
497 return os.name == 'nt' and 'command' in os.environ.get('comspec', '')
499 return os.name == 'nt' and 'command' in os.environ.get('comspec', '')
498
500
499 # Platform specific variants
501 # Platform specific variants
500 if os.name == 'nt':
502 if os.name == 'nt':
501 demandload(globals(), "msvcrt")
503 demandload(globals(), "msvcrt")
502 nulldev = 'NUL:'
504 nulldev = 'NUL:'
503
505
504 class winstdout:
506 class winstdout:
505 '''stdout on windows misbehaves if sent through a pipe'''
507 '''stdout on windows misbehaves if sent through a pipe'''
506
508
507 def __init__(self, fp):
509 def __init__(self, fp):
508 self.fp = fp
510 self.fp = fp
509
511
510 def __getattr__(self, key):
512 def __getattr__(self, key):
511 return getattr(self.fp, key)
513 return getattr(self.fp, key)
512
514
513 def close(self):
515 def close(self):
514 try:
516 try:
515 self.fp.close()
517 self.fp.close()
516 except: pass
518 except: pass
517
519
518 def write(self, s):
520 def write(self, s):
519 try:
521 try:
520 return self.fp.write(s)
522 return self.fp.write(s)
521 except IOError, inst:
523 except IOError, inst:
522 if inst.errno != 0: raise
524 if inst.errno != 0: raise
523 self.close()
525 self.close()
524 raise IOError(errno.EPIPE, 'Broken pipe')
526 raise IOError(errno.EPIPE, 'Broken pipe')
525
527
526 sys.stdout = winstdout(sys.stdout)
528 sys.stdout = winstdout(sys.stdout)
527
529
528 def system_rcpath():
530 def system_rcpath():
529 try:
531 try:
530 return system_rcpath_win32()
532 return system_rcpath_win32()
531 except:
533 except:
532 return [r'c:\mercurial\mercurial.ini']
534 return [r'c:\mercurial\mercurial.ini']
533
535
534 def os_rcpath():
536 def os_rcpath():
535 '''return default os-specific hgrc search path'''
537 '''return default os-specific hgrc search path'''
536 return system_rcpath() + [os.path.join(os.path.expanduser('~'),
538 return system_rcpath() + [os.path.join(os.path.expanduser('~'),
537 'mercurial.ini')]
539 'mercurial.ini')]
538
540
539 def parse_patch_output(output_line):
541 def parse_patch_output(output_line):
540 """parses the output produced by patch and returns the file name"""
542 """parses the output produced by patch and returns the file name"""
541 pf = output_line[14:]
543 pf = output_line[14:]
542 if pf[0] == '`':
544 if pf[0] == '`':
543 pf = pf[1:-1] # Remove the quotes
545 pf = pf[1:-1] # Remove the quotes
544 return pf
546 return pf
545
547
546 def testpid(pid):
548 def testpid(pid):
547 '''return False if pid dead, True if running or not known'''
549 '''return False if pid dead, True if running or not known'''
548 return True
550 return True
549
551
550 def is_exec(f, last):
552 def is_exec(f, last):
551 return last
553 return last
552
554
553 def set_exec(f, mode):
555 def set_exec(f, mode):
554 pass
556 pass
555
557
556 def set_binary(fd):
558 def set_binary(fd):
557 msvcrt.setmode(fd.fileno(), os.O_BINARY)
559 msvcrt.setmode(fd.fileno(), os.O_BINARY)
558
560
559 def pconvert(path):
561 def pconvert(path):
560 return path.replace("\\", "/")
562 return path.replace("\\", "/")
561
563
562 def localpath(path):
564 def localpath(path):
563 return path.replace('/', '\\')
565 return path.replace('/', '\\')
564
566
565 def normpath(path):
567 def normpath(path):
566 return pconvert(os.path.normpath(path))
568 return pconvert(os.path.normpath(path))
567
569
568 makelock = _makelock_file
570 makelock = _makelock_file
569 readlock = _readlock_file
571 readlock = _readlock_file
570
572
571 def samestat(s1, s2):
573 def samestat(s1, s2):
572 return False
574 return False
573
575
574 def explain_exit(code):
576 def explain_exit(code):
575 return _("exited with status %d") % code, code
577 return _("exited with status %d") % code, code
576
578
577 try:
579 try:
578 # override functions with win32 versions if possible
580 # override functions with win32 versions if possible
579 from util_win32 import *
581 from util_win32 import *
580 if not is_win_9x():
582 if not is_win_9x():
581 posixfile = posixfile_nt
583 posixfile = posixfile_nt
582 except ImportError:
584 except ImportError:
583 pass
585 pass
584
586
585 else:
587 else:
586 nulldev = '/dev/null'
588 nulldev = '/dev/null'
587
589
588 def rcfiles(path):
590 def rcfiles(path):
589 rcs = [os.path.join(path, 'hgrc')]
591 rcs = [os.path.join(path, 'hgrc')]
590 rcdir = os.path.join(path, 'hgrc.d')
592 rcdir = os.path.join(path, 'hgrc.d')
591 try:
593 try:
592 rcs.extend([os.path.join(rcdir, f) for f in os.listdir(rcdir)
594 rcs.extend([os.path.join(rcdir, f) for f in os.listdir(rcdir)
593 if f.endswith(".rc")])
595 if f.endswith(".rc")])
594 except OSError, inst: pass
596 except OSError, inst: pass
595 return rcs
597 return rcs
596
598
597 def os_rcpath():
599 def os_rcpath():
598 '''return default os-specific hgrc search path'''
600 '''return default os-specific hgrc search path'''
599 path = []
601 path = []
600 # old mod_python does not set sys.argv
602 # old mod_python does not set sys.argv
601 if len(getattr(sys, 'argv', [])) > 0:
603 if len(getattr(sys, 'argv', [])) > 0:
602 path.extend(rcfiles(os.path.dirname(sys.argv[0]) +
604 path.extend(rcfiles(os.path.dirname(sys.argv[0]) +
603 '/../etc/mercurial'))
605 '/../etc/mercurial'))
604 path.extend(rcfiles('/etc/mercurial'))
606 path.extend(rcfiles('/etc/mercurial'))
605 path.append(os.path.expanduser('~/.hgrc'))
607 path.append(os.path.expanduser('~/.hgrc'))
606 path = [os.path.normpath(f) for f in path]
608 path = [os.path.normpath(f) for f in path]
607 return path
609 return path
608
610
609 def parse_patch_output(output_line):
611 def parse_patch_output(output_line):
610 """parses the output produced by patch and returns the file name"""
612 """parses the output produced by patch and returns the file name"""
611 pf = output_line[14:]
613 pf = output_line[14:]
612 if pf.startswith("'") and pf.endswith("'") and pf.find(" ") >= 0:
614 if pf.startswith("'") and pf.endswith("'") and pf.find(" ") >= 0:
613 pf = pf[1:-1] # Remove the quotes
615 pf = pf[1:-1] # Remove the quotes
614 return pf
616 return pf
615
617
616 def is_exec(f, last):
618 def is_exec(f, last):
617 """check whether a file is executable"""
619 """check whether a file is executable"""
618 return (os.stat(f).st_mode & 0100 != 0)
620 return (os.stat(f).st_mode & 0100 != 0)
619
621
620 def set_exec(f, mode):
622 def set_exec(f, mode):
621 s = os.stat(f).st_mode
623 s = os.stat(f).st_mode
622 if (s & 0100 != 0) == mode:
624 if (s & 0100 != 0) == mode:
623 return
625 return
624 if mode:
626 if mode:
625 # Turn on +x for every +r bit when making a file executable
627 # Turn on +x for every +r bit when making a file executable
626 # and obey umask.
628 # and obey umask.
627 umask = os.umask(0)
629 umask = os.umask(0)
628 os.umask(umask)
630 os.umask(umask)
629 os.chmod(f, s | (s & 0444) >> 2 & ~umask)
631 os.chmod(f, s | (s & 0444) >> 2 & ~umask)
630 else:
632 else:
631 os.chmod(f, s & 0666)
633 os.chmod(f, s & 0666)
632
634
633 def set_binary(fd):
635 def set_binary(fd):
634 pass
636 pass
635
637
636 def pconvert(path):
638 def pconvert(path):
637 return path
639 return path
638
640
639 def localpath(path):
641 def localpath(path):
640 return path
642 return path
641
643
642 normpath = os.path.normpath
644 normpath = os.path.normpath
643 samestat = os.path.samestat
645 samestat = os.path.samestat
644
646
645 def makelock(info, pathname):
647 def makelock(info, pathname):
646 try:
648 try:
647 os.symlink(info, pathname)
649 os.symlink(info, pathname)
648 except OSError, why:
650 except OSError, why:
649 if why.errno == errno.EEXIST:
651 if why.errno == errno.EEXIST:
650 raise
652 raise
651 else:
653 else:
652 _makelock_file(info, pathname)
654 _makelock_file(info, pathname)
653
655
654 def readlock(pathname):
656 def readlock(pathname):
655 try:
657 try:
656 return os.readlink(pathname)
658 return os.readlink(pathname)
657 except OSError, why:
659 except OSError, why:
658 if why.errno == errno.EINVAL:
660 if why.errno == errno.EINVAL:
659 return _readlock_file(pathname)
661 return _readlock_file(pathname)
660 else:
662 else:
661 raise
663 raise
662
664
663 def testpid(pid):
665 def testpid(pid):
664 '''return False if pid dead, True if running or not sure'''
666 '''return False if pid dead, True if running or not sure'''
665 try:
667 try:
666 os.kill(pid, 0)
668 os.kill(pid, 0)
667 return True
669 return True
668 except OSError, inst:
670 except OSError, inst:
669 return inst.errno != errno.ESRCH
671 return inst.errno != errno.ESRCH
670
672
671 def explain_exit(code):
673 def explain_exit(code):
672 """return a 2-tuple (desc, code) describing a process's status"""
674 """return a 2-tuple (desc, code) describing a process's status"""
673 if os.WIFEXITED(code):
675 if os.WIFEXITED(code):
674 val = os.WEXITSTATUS(code)
676 val = os.WEXITSTATUS(code)
675 return _("exited with status %d") % val, val
677 return _("exited with status %d") % val, val
676 elif os.WIFSIGNALED(code):
678 elif os.WIFSIGNALED(code):
677 val = os.WTERMSIG(code)
679 val = os.WTERMSIG(code)
678 return _("killed by signal %d") % val, val
680 return _("killed by signal %d") % val, val
679 elif os.WIFSTOPPED(code):
681 elif os.WIFSTOPPED(code):
680 val = os.WSTOPSIG(code)
682 val = os.WSTOPSIG(code)
681 return _("stopped by signal %d") % val, val
683 return _("stopped by signal %d") % val, val
682 raise ValueError(_("invalid exit code"))
684 raise ValueError(_("invalid exit code"))
683
685
684 def opener(base, audit=True):
686 def opener(base, audit=True):
685 """
687 """
686 return a function that opens files relative to base
688 return a function that opens files relative to base
687
689
688 this function is used to hide the details of COW semantics and
690 this function is used to hide the details of COW semantics and
689 remote file access from higher level code.
691 remote file access from higher level code.
690 """
692 """
691 p = base
693 p = base
692 audit_p = audit
694 audit_p = audit
693
695
694 def mktempcopy(name):
696 def mktempcopy(name):
695 d, fn = os.path.split(name)
697 d, fn = os.path.split(name)
696 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
698 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
697 os.close(fd)
699 os.close(fd)
698 ofp = posixfile(temp, "wb")
700 ofp = posixfile(temp, "wb")
699 try:
701 try:
700 try:
702 try:
701 ifp = posixfile(name, "rb")
703 ifp = posixfile(name, "rb")
702 except IOError, inst:
704 except IOError, inst:
703 if not getattr(inst, 'filename', None):
705 if not getattr(inst, 'filename', None):
704 inst.filename = name
706 inst.filename = name
705 raise
707 raise
706 for chunk in filechunkiter(ifp):
708 for chunk in filechunkiter(ifp):
707 ofp.write(chunk)
709 ofp.write(chunk)
708 ifp.close()
710 ifp.close()
709 ofp.close()
711 ofp.close()
710 except:
712 except:
711 try: os.unlink(temp)
713 try: os.unlink(temp)
712 except: pass
714 except: pass
713 raise
715 raise
714 st = os.lstat(name)
716 st = os.lstat(name)
715 os.chmod(temp, st.st_mode)
717 os.chmod(temp, st.st_mode)
716 return temp
718 return temp
717
719
718 class atomictempfile(posixfile):
720 class atomictempfile(posixfile):
719 """the file will only be copied when rename is called"""
721 """the file will only be copied when rename is called"""
720 def __init__(self, name, mode):
722 def __init__(self, name, mode):
721 self.__name = name
723 self.__name = name
722 self.temp = mktempcopy(name)
724 self.temp = mktempcopy(name)
723 posixfile.__init__(self, self.temp, mode)
725 posixfile.__init__(self, self.temp, mode)
724 def rename(self):
726 def rename(self):
725 if not self.closed:
727 if not self.closed:
726 posixfile.close(self)
728 posixfile.close(self)
727 rename(self.temp, self.__name)
729 rename(self.temp, self.__name)
728 def __del__(self):
730 def __del__(self):
729 if not self.closed:
731 if not self.closed:
730 try:
732 try:
731 os.unlink(self.temp)
733 os.unlink(self.temp)
732 except: pass
734 except: pass
733 posixfile.close(self)
735 posixfile.close(self)
734
736
735 class atomicfile(atomictempfile):
737 class atomicfile(atomictempfile):
736 """the file will only be copied on close"""
738 """the file will only be copied on close"""
737 def __init__(self, name, mode):
739 def __init__(self, name, mode):
738 atomictempfile.__init__(self, name, mode)
740 atomictempfile.__init__(self, name, mode)
739 def close(self):
741 def close(self):
740 self.rename()
742 self.rename()
741 def __del__(self):
743 def __del__(self):
742 self.rename()
744 self.rename()
743
745
744 def o(path, mode="r", text=False, atomic=False, atomictemp=False):
746 def o(path, mode="r", text=False, atomic=False, atomictemp=False):
745 if audit_p:
747 if audit_p:
746 audit_path(path)
748 audit_path(path)
747 f = os.path.join(p, path)
749 f = os.path.join(p, path)
748
750
749 if not text:
751 if not text:
750 mode += "b" # for that other OS
752 mode += "b" # for that other OS
751
753
752 if mode[0] != "r":
754 if mode[0] != "r":
753 try:
755 try:
754 nlink = nlinks(f)
756 nlink = nlinks(f)
755 except OSError:
757 except OSError:
756 d = os.path.dirname(f)
758 d = os.path.dirname(f)
757 if not os.path.isdir(d):
759 if not os.path.isdir(d):
758 os.makedirs(d)
760 os.makedirs(d)
759 else:
761 else:
760 if atomic:
762 if atomic:
761 return atomicfile(f, mode)
763 return atomicfile(f, mode)
762 elif atomictemp:
764 elif atomictemp:
763 return atomictempfile(f, mode)
765 return atomictempfile(f, mode)
764 if nlink > 1:
766 if nlink > 1:
765 rename(mktempcopy(f), f)
767 rename(mktempcopy(f), f)
766 return posixfile(f, mode)
768 return posixfile(f, mode)
767
769
768 return o
770 return o
769
771
770 class chunkbuffer(object):
772 class chunkbuffer(object):
771 """Allow arbitrary sized chunks of data to be efficiently read from an
773 """Allow arbitrary sized chunks of data to be efficiently read from an
772 iterator over chunks of arbitrary size."""
774 iterator over chunks of arbitrary size."""
773
775
774 def __init__(self, in_iter, targetsize = 2**16):
776 def __init__(self, in_iter, targetsize = 2**16):
775 """in_iter is the iterator that's iterating over the input chunks.
777 """in_iter is the iterator that's iterating over the input chunks.
776 targetsize is how big a buffer to try to maintain."""
778 targetsize is how big a buffer to try to maintain."""
777 self.in_iter = iter(in_iter)
779 self.in_iter = iter(in_iter)
778 self.buf = ''
780 self.buf = ''
779 self.targetsize = int(targetsize)
781 self.targetsize = int(targetsize)
780 if self.targetsize <= 0:
782 if self.targetsize <= 0:
781 raise ValueError(_("targetsize must be greater than 0, was %d") %
783 raise ValueError(_("targetsize must be greater than 0, was %d") %
782 targetsize)
784 targetsize)
783 self.iterempty = False
785 self.iterempty = False
784
786
785 def fillbuf(self):
787 def fillbuf(self):
786 """Ignore target size; read every chunk from iterator until empty."""
788 """Ignore target size; read every chunk from iterator until empty."""
787 if not self.iterempty:
789 if not self.iterempty:
788 collector = cStringIO.StringIO()
790 collector = cStringIO.StringIO()
789 collector.write(self.buf)
791 collector.write(self.buf)
790 for ch in self.in_iter:
792 for ch in self.in_iter:
791 collector.write(ch)
793 collector.write(ch)
792 self.buf = collector.getvalue()
794 self.buf = collector.getvalue()
793 self.iterempty = True
795 self.iterempty = True
794
796
795 def read(self, l):
797 def read(self, l):
796 """Read L bytes of data from the iterator of chunks of data.
798 """Read L bytes of data from the iterator of chunks of data.
797 Returns less than L bytes if the iterator runs dry."""
799 Returns less than L bytes if the iterator runs dry."""
798 if l > len(self.buf) and not self.iterempty:
800 if l > len(self.buf) and not self.iterempty:
799 # Clamp to a multiple of self.targetsize
801 # Clamp to a multiple of self.targetsize
800 targetsize = self.targetsize * ((l // self.targetsize) + 1)
802 targetsize = self.targetsize * ((l // self.targetsize) + 1)
801 collector = cStringIO.StringIO()
803 collector = cStringIO.StringIO()
802 collector.write(self.buf)
804 collector.write(self.buf)
803 collected = len(self.buf)
805 collected = len(self.buf)
804 for chunk in self.in_iter:
806 for chunk in self.in_iter:
805 collector.write(chunk)
807 collector.write(chunk)
806 collected += len(chunk)
808 collected += len(chunk)
807 if collected >= targetsize:
809 if collected >= targetsize:
808 break
810 break
809 if collected < targetsize:
811 if collected < targetsize:
810 self.iterempty = True
812 self.iterempty = True
811 self.buf = collector.getvalue()
813 self.buf = collector.getvalue()
812 s, self.buf = self.buf[:l], buffer(self.buf, l)
814 s, self.buf = self.buf[:l], buffer(self.buf, l)
813 return s
815 return s
814
816
815 def filechunkiter(f, size = 65536):
817 def filechunkiter(f, size = 65536):
816 """Create a generator that produces all the data in the file size
818 """Create a generator that produces all the data in the file size
817 (default 65536) bytes at a time. Chunks may be less than size
819 (default 65536) bytes at a time. Chunks may be less than size
818 bytes if the chunk is the last chunk in the file, or the file is a
820 bytes if the chunk is the last chunk in the file, or the file is a
819 socket or some other type of file that sometimes reads less data
821 socket or some other type of file that sometimes reads less data
820 than is requested."""
822 than is requested."""
821 s = f.read(size)
823 s = f.read(size)
822 while len(s) > 0:
824 while len(s) > 0:
823 yield s
825 yield s
824 s = f.read(size)
826 s = f.read(size)
825
827
826 def makedate():
828 def makedate():
827 lt = time.localtime()
829 lt = time.localtime()
828 if lt[8] == 1 and time.daylight:
830 if lt[8] == 1 and time.daylight:
829 tz = time.altzone
831 tz = time.altzone
830 else:
832 else:
831 tz = time.timezone
833 tz = time.timezone
832 return time.mktime(lt), tz
834 return time.mktime(lt), tz
833
835
834 def datestr(date=None, format='%a %b %d %H:%M:%S %Y', timezone=True):
836 def datestr(date=None, format='%a %b %d %H:%M:%S %Y', timezone=True):
835 """represent a (unixtime, offset) tuple as a localized time.
837 """represent a (unixtime, offset) tuple as a localized time.
836 unixtime is seconds since the epoch, and offset is the time zone's
838 unixtime is seconds since the epoch, and offset is the time zone's
837 number of seconds away from UTC. if timezone is false, do not
839 number of seconds away from UTC. if timezone is false, do not
838 append time zone to string."""
840 append time zone to string."""
839 t, tz = date or makedate()
841 t, tz = date or makedate()
840 s = time.strftime(format, time.gmtime(float(t) - tz))
842 s = time.strftime(format, time.gmtime(float(t) - tz))
841 if timezone:
843 if timezone:
842 s += " %+03d%02d" % (-tz / 3600, ((-tz % 3600) / 60))
844 s += " %+03d%02d" % (-tz / 3600, ((-tz % 3600) / 60))
843 return s
845 return s
844
846
845 def shortuser(user):
847 def shortuser(user):
846 """Return a short representation of a user name or email address."""
848 """Return a short representation of a user name or email address."""
847 f = user.find('@')
849 f = user.find('@')
848 if f >= 0:
850 if f >= 0:
849 user = user[:f]
851 user = user[:f]
850 f = user.find('<')
852 f = user.find('<')
851 if f >= 0:
853 if f >= 0:
852 user = user[f+1:]
854 user = user[f+1:]
853 return user
855 return user
854
856
855 def walkrepos(path):
857 def walkrepos(path):
856 '''yield every hg repository under path, recursively.'''
858 '''yield every hg repository under path, recursively.'''
857 def errhandler(err):
859 def errhandler(err):
858 if err.filename == path:
860 if err.filename == path:
859 raise err
861 raise err
860
862
861 for root, dirs, files in os.walk(path, onerror=errhandler):
863 for root, dirs, files in os.walk(path, onerror=errhandler):
862 for d in dirs:
864 for d in dirs:
863 if d == '.hg':
865 if d == '.hg':
864 yield root
866 yield root
865 dirs[:] = []
867 dirs[:] = []
866 break
868 break
867
869
868 _rcpath = None
870 _rcpath = None
869
871
870 def rcpath():
872 def rcpath():
871 '''return hgrc search path. if env var HGRCPATH is set, use it.
873 '''return hgrc search path. if env var HGRCPATH is set, use it.
872 for each item in path, if directory, use files ending in .rc,
874 for each item in path, if directory, use files ending in .rc,
873 else use item.
875 else use item.
874 make HGRCPATH empty to only look in .hg/hgrc of current repo.
876 make HGRCPATH empty to only look in .hg/hgrc of current repo.
875 if no HGRCPATH, use default os-specific path.'''
877 if no HGRCPATH, use default os-specific path.'''
876 global _rcpath
878 global _rcpath
877 if _rcpath is None:
879 if _rcpath is None:
878 if 'HGRCPATH' in os.environ:
880 if 'HGRCPATH' in os.environ:
879 _rcpath = []
881 _rcpath = []
880 for p in os.environ['HGRCPATH'].split(os.pathsep):
882 for p in os.environ['HGRCPATH'].split(os.pathsep):
881 if not p: continue
883 if not p: continue
882 if os.path.isdir(p):
884 if os.path.isdir(p):
883 for f in os.listdir(p):
885 for f in os.listdir(p):
884 if f.endswith('.rc'):
886 if f.endswith('.rc'):
885 _rcpath.append(os.path.join(p, f))
887 _rcpath.append(os.path.join(p, f))
886 else:
888 else:
887 _rcpath.append(p)
889 _rcpath.append(p)
888 else:
890 else:
889 _rcpath = os_rcpath()
891 _rcpath = os_rcpath()
890 return _rcpath
892 return _rcpath
General Comments 0
You need to be logged in to leave comments. Login now