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