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