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