##// END OF EJS Templates
util: always use subprocess
Martin Geisler -
r8280:0b02d98d default
parent child Browse files
Show More
@@ -1,1493 +1,1485
1 # util.py - Mercurial utility functions and platform specfic implementations
1 # util.py - Mercurial utility functions and platform specfic implementations
2 #
2 #
3 # Copyright 2005 K. Thananchayan <thananck@yahoo.com>
3 # Copyright 2005 K. Thananchayan <thananck@yahoo.com>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
6 #
6 #
7 # This software may be used and distributed according to the terms of the
7 # This software may be used and distributed according to the terms of the
8 # GNU General Public License version 2, incorporated herein by reference.
8 # GNU General Public License version 2, incorporated herein by reference.
9
9
10 """Mercurial utility functions and platform specfic implementations.
10 """Mercurial utility functions and platform specfic implementations.
11
11
12 This contains helper routines that are independent of the SCM core and
12 This contains helper routines that are independent of the SCM core and
13 hide platform-specific details from the core.
13 hide platform-specific details from the core.
14 """
14 """
15
15
16 from i18n import _
16 from i18n import _
17 import cStringIO, errno, re, shutil, sys, tempfile, traceback, error
17 import cStringIO, errno, re, shutil, sys, tempfile, traceback, error
18 import os, stat, threading, time, calendar, glob, osutil, random
18 import os, stat, threading, time, calendar, glob, osutil, random
19 import imp
19 import imp
20
20
21 # Python compatibility
21 # Python compatibility
22
22
23 _md5 = None
23 _md5 = None
24 def md5(s):
24 def md5(s):
25 global _md5
25 global _md5
26 if _md5 is None:
26 if _md5 is None:
27 try:
27 try:
28 import hashlib
28 import hashlib
29 _md5 = hashlib.md5
29 _md5 = hashlib.md5
30 except ImportError:
30 except ImportError:
31 import md5
31 import md5
32 _md5 = md5.md5
32 _md5 = md5.md5
33 return _md5(s)
33 return _md5(s)
34
34
35 _sha1 = None
35 _sha1 = None
36 def sha1(s):
36 def sha1(s):
37 global _sha1
37 global _sha1
38 if _sha1 is None:
38 if _sha1 is None:
39 try:
39 try:
40 import hashlib
40 import hashlib
41 _sha1 = hashlib.sha1
41 _sha1 = hashlib.sha1
42 except ImportError:
42 except ImportError:
43 import sha
43 import sha
44 _sha1 = sha.sha
44 _sha1 = sha.sha
45 return _sha1(s)
45 return _sha1(s)
46
46
47 try:
47 import subprocess
48 import subprocess
48 closefds = os.name == 'posix'
49 subprocess.Popen # trigger ImportError early
49 def popen2(cmd, mode='t', bufsize=-1):
50 closefds = os.name == 'posix'
50 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
51 def popen2(cmd, mode='t', bufsize=-1):
51 close_fds=closefds,
52 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
52 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
53 close_fds=closefds,
53 return p.stdin, p.stdout
54 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
54 def popen3(cmd, mode='t', bufsize=-1):
55 return p.stdin, p.stdout
55 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
56 def popen3(cmd, mode='t', bufsize=-1):
56 close_fds=closefds,
57 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
57 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
58 close_fds=closefds,
58 stderr=subprocess.PIPE)
59 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
59 return p.stdin, p.stdout, p.stderr
60 stderr=subprocess.PIPE)
60 def Popen3(cmd, capturestderr=False, bufsize=-1):
61 return p.stdin, p.stdout, p.stderr
61 stderr = capturestderr and subprocess.PIPE or None
62 def Popen3(cmd, capturestderr=False, bufsize=-1):
62 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
63 stderr = capturestderr and subprocess.PIPE or None
63 close_fds=closefds,
64 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
64 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
65 close_fds=closefds,
65 stderr=stderr)
66 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
66 p.fromchild = p.stdout
67 stderr=stderr)
67 p.tochild = p.stdin
68 p.fromchild = p.stdout
68 p.childerr = p.stderr
69 p.tochild = p.stdin
69 return p
70 p.childerr = p.stderr
71 return p
72 except ImportError:
73 subprocess = None
74 from popen2 import Popen3
75 popen2 = os.popen2
76 popen3 = os.popen3
77
78
70
79 def version():
71 def version():
80 """Return version information if available."""
72 """Return version information if available."""
81 try:
73 try:
82 import __version__
74 import __version__
83 return __version__.version
75 return __version__.version
84 except ImportError:
76 except ImportError:
85 return 'unknown'
77 return 'unknown'
86
78
87 # used by parsedate
79 # used by parsedate
88 defaultdateformats = (
80 defaultdateformats = (
89 '%Y-%m-%d %H:%M:%S',
81 '%Y-%m-%d %H:%M:%S',
90 '%Y-%m-%d %I:%M:%S%p',
82 '%Y-%m-%d %I:%M:%S%p',
91 '%Y-%m-%d %H:%M',
83 '%Y-%m-%d %H:%M',
92 '%Y-%m-%d %I:%M%p',
84 '%Y-%m-%d %I:%M%p',
93 '%Y-%m-%d',
85 '%Y-%m-%d',
94 '%m-%d',
86 '%m-%d',
95 '%m/%d',
87 '%m/%d',
96 '%m/%d/%y',
88 '%m/%d/%y',
97 '%m/%d/%Y',
89 '%m/%d/%Y',
98 '%a %b %d %H:%M:%S %Y',
90 '%a %b %d %H:%M:%S %Y',
99 '%a %b %d %I:%M:%S%p %Y',
91 '%a %b %d %I:%M:%S%p %Y',
100 '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
92 '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
101 '%b %d %H:%M:%S %Y',
93 '%b %d %H:%M:%S %Y',
102 '%b %d %I:%M:%S%p %Y',
94 '%b %d %I:%M:%S%p %Y',
103 '%b %d %H:%M:%S',
95 '%b %d %H:%M:%S',
104 '%b %d %I:%M:%S%p',
96 '%b %d %I:%M:%S%p',
105 '%b %d %H:%M',
97 '%b %d %H:%M',
106 '%b %d %I:%M%p',
98 '%b %d %I:%M%p',
107 '%b %d %Y',
99 '%b %d %Y',
108 '%b %d',
100 '%b %d',
109 '%H:%M:%S',
101 '%H:%M:%S',
110 '%I:%M:%SP',
102 '%I:%M:%SP',
111 '%H:%M',
103 '%H:%M',
112 '%I:%M%p',
104 '%I:%M%p',
113 )
105 )
114
106
115 extendeddateformats = defaultdateformats + (
107 extendeddateformats = defaultdateformats + (
116 "%Y",
108 "%Y",
117 "%Y-%m",
109 "%Y-%m",
118 "%b",
110 "%b",
119 "%b %Y",
111 "%b %Y",
120 )
112 )
121
113
122 def cachefunc(func):
114 def cachefunc(func):
123 '''cache the result of function calls'''
115 '''cache the result of function calls'''
124 # XXX doesn't handle keywords args
116 # XXX doesn't handle keywords args
125 cache = {}
117 cache = {}
126 if func.func_code.co_argcount == 1:
118 if func.func_code.co_argcount == 1:
127 # we gain a small amount of time because
119 # we gain a small amount of time because
128 # we don't need to pack/unpack the list
120 # we don't need to pack/unpack the list
129 def f(arg):
121 def f(arg):
130 if arg not in cache:
122 if arg not in cache:
131 cache[arg] = func(arg)
123 cache[arg] = func(arg)
132 return cache[arg]
124 return cache[arg]
133 else:
125 else:
134 def f(*args):
126 def f(*args):
135 if args not in cache:
127 if args not in cache:
136 cache[args] = func(*args)
128 cache[args] = func(*args)
137 return cache[args]
129 return cache[args]
138
130
139 return f
131 return f
140
132
141 class propertycache(object):
133 class propertycache(object):
142 def __init__(self, func):
134 def __init__(self, func):
143 self.func = func
135 self.func = func
144 self.name = func.__name__
136 self.name = func.__name__
145 def __get__(self, obj, type=None):
137 def __get__(self, obj, type=None):
146 result = self.func(obj)
138 result = self.func(obj)
147 setattr(obj, self.name, result)
139 setattr(obj, self.name, result)
148 return result
140 return result
149
141
150 def pipefilter(s, cmd):
142 def pipefilter(s, cmd):
151 '''filter string S through command CMD, returning its output'''
143 '''filter string S through command CMD, returning its output'''
152 (pin, pout) = popen2(cmd, 'b')
144 (pin, pout) = popen2(cmd, 'b')
153 def writer():
145 def writer():
154 try:
146 try:
155 pin.write(s)
147 pin.write(s)
156 pin.close()
148 pin.close()
157 except IOError, inst:
149 except IOError, inst:
158 if inst.errno != errno.EPIPE:
150 if inst.errno != errno.EPIPE:
159 raise
151 raise
160
152
161 # we should use select instead on UNIX, but this will work on most
153 # we should use select instead on UNIX, but this will work on most
162 # systems, including Windows
154 # systems, including Windows
163 w = threading.Thread(target=writer)
155 w = threading.Thread(target=writer)
164 w.start()
156 w.start()
165 f = pout.read()
157 f = pout.read()
166 pout.close()
158 pout.close()
167 w.join()
159 w.join()
168 return f
160 return f
169
161
170 def tempfilter(s, cmd):
162 def tempfilter(s, cmd):
171 '''filter string S through a pair of temporary files with CMD.
163 '''filter string S through a pair of temporary files with CMD.
172 CMD is used as a template to create the real command to be run,
164 CMD is used as a template to create the real command to be run,
173 with the strings INFILE and OUTFILE replaced by the real names of
165 with the strings INFILE and OUTFILE replaced by the real names of
174 the temporary files generated.'''
166 the temporary files generated.'''
175 inname, outname = None, None
167 inname, outname = None, None
176 try:
168 try:
177 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
169 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
178 fp = os.fdopen(infd, 'wb')
170 fp = os.fdopen(infd, 'wb')
179 fp.write(s)
171 fp.write(s)
180 fp.close()
172 fp.close()
181 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
173 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
182 os.close(outfd)
174 os.close(outfd)
183 cmd = cmd.replace('INFILE', inname)
175 cmd = cmd.replace('INFILE', inname)
184 cmd = cmd.replace('OUTFILE', outname)
176 cmd = cmd.replace('OUTFILE', outname)
185 code = os.system(cmd)
177 code = os.system(cmd)
186 if sys.platform == 'OpenVMS' and code & 1:
178 if sys.platform == 'OpenVMS' and code & 1:
187 code = 0
179 code = 0
188 if code: raise Abort(_("command '%s' failed: %s") %
180 if code: raise Abort(_("command '%s' failed: %s") %
189 (cmd, explain_exit(code)))
181 (cmd, explain_exit(code)))
190 return open(outname, 'rb').read()
182 return open(outname, 'rb').read()
191 finally:
183 finally:
192 try:
184 try:
193 if inname: os.unlink(inname)
185 if inname: os.unlink(inname)
194 except: pass
186 except: pass
195 try:
187 try:
196 if outname: os.unlink(outname)
188 if outname: os.unlink(outname)
197 except: pass
189 except: pass
198
190
199 filtertable = {
191 filtertable = {
200 'tempfile:': tempfilter,
192 'tempfile:': tempfilter,
201 'pipe:': pipefilter,
193 'pipe:': pipefilter,
202 }
194 }
203
195
204 def filter(s, cmd):
196 def filter(s, cmd):
205 "filter a string through a command that transforms its input to its output"
197 "filter a string through a command that transforms its input to its output"
206 for name, fn in filtertable.iteritems():
198 for name, fn in filtertable.iteritems():
207 if cmd.startswith(name):
199 if cmd.startswith(name):
208 return fn(s, cmd[len(name):].lstrip())
200 return fn(s, cmd[len(name):].lstrip())
209 return pipefilter(s, cmd)
201 return pipefilter(s, cmd)
210
202
211 def binary(s):
203 def binary(s):
212 """return true if a string is binary data"""
204 """return true if a string is binary data"""
213 return bool(s and '\0' in s)
205 return bool(s and '\0' in s)
214
206
215 def increasingchunks(source, min=1024, max=65536):
207 def increasingchunks(source, min=1024, max=65536):
216 '''return no less than min bytes per chunk while data remains,
208 '''return no less than min bytes per chunk while data remains,
217 doubling min after each chunk until it reaches max'''
209 doubling min after each chunk until it reaches max'''
218 def log2(x):
210 def log2(x):
219 if not x:
211 if not x:
220 return 0
212 return 0
221 i = 0
213 i = 0
222 while x:
214 while x:
223 x >>= 1
215 x >>= 1
224 i += 1
216 i += 1
225 return i - 1
217 return i - 1
226
218
227 buf = []
219 buf = []
228 blen = 0
220 blen = 0
229 for chunk in source:
221 for chunk in source:
230 buf.append(chunk)
222 buf.append(chunk)
231 blen += len(chunk)
223 blen += len(chunk)
232 if blen >= min:
224 if blen >= min:
233 if min < max:
225 if min < max:
234 min = min << 1
226 min = min << 1
235 nmin = 1 << log2(blen)
227 nmin = 1 << log2(blen)
236 if nmin > min:
228 if nmin > min:
237 min = nmin
229 min = nmin
238 if min > max:
230 if min > max:
239 min = max
231 min = max
240 yield ''.join(buf)
232 yield ''.join(buf)
241 blen = 0
233 blen = 0
242 buf = []
234 buf = []
243 if buf:
235 if buf:
244 yield ''.join(buf)
236 yield ''.join(buf)
245
237
246 Abort = error.Abort
238 Abort = error.Abort
247
239
248 def always(fn): return True
240 def always(fn): return True
249 def never(fn): return False
241 def never(fn): return False
250
242
251 def patkind(name, default):
243 def patkind(name, default):
252 """Split a string into an optional pattern kind prefix and the
244 """Split a string into an optional pattern kind prefix and the
253 actual pattern."""
245 actual pattern."""
254 for prefix in 're', 'glob', 'path', 'relglob', 'relpath', 'relre':
246 for prefix in 're', 'glob', 'path', 'relglob', 'relpath', 'relre':
255 if name.startswith(prefix + ':'): return name.split(':', 1)
247 if name.startswith(prefix + ':'): return name.split(':', 1)
256 return default, name
248 return default, name
257
249
258 def globre(pat, head='^', tail='$'):
250 def globre(pat, head='^', tail='$'):
259 "convert a glob pattern into a regexp"
251 "convert a glob pattern into a regexp"
260 i, n = 0, len(pat)
252 i, n = 0, len(pat)
261 res = ''
253 res = ''
262 group = 0
254 group = 0
263 def peek(): return i < n and pat[i]
255 def peek(): return i < n and pat[i]
264 while i < n:
256 while i < n:
265 c = pat[i]
257 c = pat[i]
266 i = i+1
258 i = i+1
267 if c == '*':
259 if c == '*':
268 if peek() == '*':
260 if peek() == '*':
269 i += 1
261 i += 1
270 res += '.*'
262 res += '.*'
271 else:
263 else:
272 res += '[^/]*'
264 res += '[^/]*'
273 elif c == '?':
265 elif c == '?':
274 res += '.'
266 res += '.'
275 elif c == '[':
267 elif c == '[':
276 j = i
268 j = i
277 if j < n and pat[j] in '!]':
269 if j < n and pat[j] in '!]':
278 j += 1
270 j += 1
279 while j < n and pat[j] != ']':
271 while j < n and pat[j] != ']':
280 j += 1
272 j += 1
281 if j >= n:
273 if j >= n:
282 res += '\\['
274 res += '\\['
283 else:
275 else:
284 stuff = pat[i:j].replace('\\','\\\\')
276 stuff = pat[i:j].replace('\\','\\\\')
285 i = j + 1
277 i = j + 1
286 if stuff[0] == '!':
278 if stuff[0] == '!':
287 stuff = '^' + stuff[1:]
279 stuff = '^' + stuff[1:]
288 elif stuff[0] == '^':
280 elif stuff[0] == '^':
289 stuff = '\\' + stuff
281 stuff = '\\' + stuff
290 res = '%s[%s]' % (res, stuff)
282 res = '%s[%s]' % (res, stuff)
291 elif c == '{':
283 elif c == '{':
292 group += 1
284 group += 1
293 res += '(?:'
285 res += '(?:'
294 elif c == '}' and group:
286 elif c == '}' and group:
295 res += ')'
287 res += ')'
296 group -= 1
288 group -= 1
297 elif c == ',' and group:
289 elif c == ',' and group:
298 res += '|'
290 res += '|'
299 elif c == '\\':
291 elif c == '\\':
300 p = peek()
292 p = peek()
301 if p:
293 if p:
302 i += 1
294 i += 1
303 res += re.escape(p)
295 res += re.escape(p)
304 else:
296 else:
305 res += re.escape(c)
297 res += re.escape(c)
306 else:
298 else:
307 res += re.escape(c)
299 res += re.escape(c)
308 return head + res + tail
300 return head + res + tail
309
301
310 _globchars = {'[': 1, '{': 1, '*': 1, '?': 1}
302 _globchars = {'[': 1, '{': 1, '*': 1, '?': 1}
311
303
312 def pathto(root, n1, n2):
304 def pathto(root, n1, n2):
313 '''return the relative path from one place to another.
305 '''return the relative path from one place to another.
314 root should use os.sep to separate directories
306 root should use os.sep to separate directories
315 n1 should use os.sep to separate directories
307 n1 should use os.sep to separate directories
316 n2 should use "/" to separate directories
308 n2 should use "/" to separate directories
317 returns an os.sep-separated path.
309 returns an os.sep-separated path.
318
310
319 If n1 is a relative path, it's assumed it's
311 If n1 is a relative path, it's assumed it's
320 relative to root.
312 relative to root.
321 n2 should always be relative to root.
313 n2 should always be relative to root.
322 '''
314 '''
323 if not n1: return localpath(n2)
315 if not n1: return localpath(n2)
324 if os.path.isabs(n1):
316 if os.path.isabs(n1):
325 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
317 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
326 return os.path.join(root, localpath(n2))
318 return os.path.join(root, localpath(n2))
327 n2 = '/'.join((pconvert(root), n2))
319 n2 = '/'.join((pconvert(root), n2))
328 a, b = splitpath(n1), n2.split('/')
320 a, b = splitpath(n1), n2.split('/')
329 a.reverse()
321 a.reverse()
330 b.reverse()
322 b.reverse()
331 while a and b and a[-1] == b[-1]:
323 while a and b and a[-1] == b[-1]:
332 a.pop()
324 a.pop()
333 b.pop()
325 b.pop()
334 b.reverse()
326 b.reverse()
335 return os.sep.join((['..'] * len(a)) + b) or '.'
327 return os.sep.join((['..'] * len(a)) + b) or '.'
336
328
337 def canonpath(root, cwd, myname):
329 def canonpath(root, cwd, myname):
338 """return the canonical path of myname, given cwd and root"""
330 """return the canonical path of myname, given cwd and root"""
339 if root == os.sep:
331 if root == os.sep:
340 rootsep = os.sep
332 rootsep = os.sep
341 elif endswithsep(root):
333 elif endswithsep(root):
342 rootsep = root
334 rootsep = root
343 else:
335 else:
344 rootsep = root + os.sep
336 rootsep = root + os.sep
345 name = myname
337 name = myname
346 if not os.path.isabs(name):
338 if not os.path.isabs(name):
347 name = os.path.join(root, cwd, name)
339 name = os.path.join(root, cwd, name)
348 name = os.path.normpath(name)
340 name = os.path.normpath(name)
349 audit_path = path_auditor(root)
341 audit_path = path_auditor(root)
350 if name != rootsep and name.startswith(rootsep):
342 if name != rootsep and name.startswith(rootsep):
351 name = name[len(rootsep):]
343 name = name[len(rootsep):]
352 audit_path(name)
344 audit_path(name)
353 return pconvert(name)
345 return pconvert(name)
354 elif name == root:
346 elif name == root:
355 return ''
347 return ''
356 else:
348 else:
357 # Determine whether `name' is in the hierarchy at or beneath `root',
349 # Determine whether `name' is in the hierarchy at or beneath `root',
358 # by iterating name=dirname(name) until that causes no change (can't
350 # by iterating name=dirname(name) until that causes no change (can't
359 # check name == '/', because that doesn't work on windows). For each
351 # check name == '/', because that doesn't work on windows). For each
360 # `name', compare dev/inode numbers. If they match, the list `rel'
352 # `name', compare dev/inode numbers. If they match, the list `rel'
361 # holds the reversed list of components making up the relative file
353 # holds the reversed list of components making up the relative file
362 # name we want.
354 # name we want.
363 root_st = os.stat(root)
355 root_st = os.stat(root)
364 rel = []
356 rel = []
365 while True:
357 while True:
366 try:
358 try:
367 name_st = os.stat(name)
359 name_st = os.stat(name)
368 except OSError:
360 except OSError:
369 break
361 break
370 if samestat(name_st, root_st):
362 if samestat(name_st, root_st):
371 if not rel:
363 if not rel:
372 # name was actually the same as root (maybe a symlink)
364 # name was actually the same as root (maybe a symlink)
373 return ''
365 return ''
374 rel.reverse()
366 rel.reverse()
375 name = os.path.join(*rel)
367 name = os.path.join(*rel)
376 audit_path(name)
368 audit_path(name)
377 return pconvert(name)
369 return pconvert(name)
378 dirname, basename = os.path.split(name)
370 dirname, basename = os.path.split(name)
379 rel.append(basename)
371 rel.append(basename)
380 if dirname == name:
372 if dirname == name:
381 break
373 break
382 name = dirname
374 name = dirname
383
375
384 raise Abort('%s not under root' % myname)
376 raise Abort('%s not under root' % myname)
385
377
386 def matcher(canonroot, cwd='', names=[], inc=[], exc=[], src=None, dflt_pat='glob'):
378 def matcher(canonroot, cwd='', names=[], inc=[], exc=[], src=None, dflt_pat='glob'):
387 """build a function to match a set of file patterns
379 """build a function to match a set of file patterns
388
380
389 arguments:
381 arguments:
390 canonroot - the canonical root of the tree you're matching against
382 canonroot - the canonical root of the tree you're matching against
391 cwd - the current working directory, if relevant
383 cwd - the current working directory, if relevant
392 names - patterns to find
384 names - patterns to find
393 inc - patterns to include
385 inc - patterns to include
394 exc - patterns to exclude
386 exc - patterns to exclude
395 dflt_pat - if a pattern in names has no explicit type, assume this one
387 dflt_pat - if a pattern in names has no explicit type, assume this one
396 src - where these patterns came from (e.g. .hgignore)
388 src - where these patterns came from (e.g. .hgignore)
397
389
398 a pattern is one of:
390 a pattern is one of:
399 'glob:<glob>' - a glob relative to cwd
391 'glob:<glob>' - a glob relative to cwd
400 're:<regexp>' - a regular expression
392 're:<regexp>' - a regular expression
401 'path:<path>' - a path relative to canonroot
393 'path:<path>' - a path relative to canonroot
402 'relglob:<glob>' - an unrooted glob (*.c matches C files in all dirs)
394 'relglob:<glob>' - an unrooted glob (*.c matches C files in all dirs)
403 'relpath:<path>' - a path relative to cwd
395 'relpath:<path>' - a path relative to cwd
404 'relre:<regexp>' - a regexp that doesn't have to match the start of a name
396 'relre:<regexp>' - a regexp that doesn't have to match the start of a name
405 '<something>' - one of the cases above, selected by the dflt_pat argument
397 '<something>' - one of the cases above, selected by the dflt_pat argument
406
398
407 returns:
399 returns:
408 a 3-tuple containing
400 a 3-tuple containing
409 - list of roots (places where one should start a recursive walk of the fs);
401 - list of roots (places where one should start a recursive walk of the fs);
410 this often matches the explicit non-pattern names passed in, but also
402 this often matches the explicit non-pattern names passed in, but also
411 includes the initial part of glob: patterns that has no glob characters
403 includes the initial part of glob: patterns that has no glob characters
412 - a bool match(filename) function
404 - a bool match(filename) function
413 - a bool indicating if any patterns were passed in
405 - a bool indicating if any patterns were passed in
414 """
406 """
415
407
416 # a common case: no patterns at all
408 # a common case: no patterns at all
417 if not names and not inc and not exc:
409 if not names and not inc and not exc:
418 return [], always, False
410 return [], always, False
419
411
420 def contains_glob(name):
412 def contains_glob(name):
421 for c in name:
413 for c in name:
422 if c in _globchars: return True
414 if c in _globchars: return True
423 return False
415 return False
424
416
425 def regex(kind, name, tail):
417 def regex(kind, name, tail):
426 '''convert a pattern into a regular expression'''
418 '''convert a pattern into a regular expression'''
427 if not name:
419 if not name:
428 return ''
420 return ''
429 if kind == 're':
421 if kind == 're':
430 return name
422 return name
431 elif kind == 'path':
423 elif kind == 'path':
432 return '^' + re.escape(name) + '(?:/|$)'
424 return '^' + re.escape(name) + '(?:/|$)'
433 elif kind == 'relglob':
425 elif kind == 'relglob':
434 return globre(name, '(?:|.*/)', tail)
426 return globre(name, '(?:|.*/)', tail)
435 elif kind == 'relpath':
427 elif kind == 'relpath':
436 return re.escape(name) + '(?:/|$)'
428 return re.escape(name) + '(?:/|$)'
437 elif kind == 'relre':
429 elif kind == 'relre':
438 if name.startswith('^'):
430 if name.startswith('^'):
439 return name
431 return name
440 return '.*' + name
432 return '.*' + name
441 return globre(name, '', tail)
433 return globre(name, '', tail)
442
434
443 def matchfn(pats, tail):
435 def matchfn(pats, tail):
444 """build a matching function from a set of patterns"""
436 """build a matching function from a set of patterns"""
445 if not pats:
437 if not pats:
446 return
438 return
447 try:
439 try:
448 pat = '(?:%s)' % '|'.join([regex(k, p, tail) for (k, p) in pats])
440 pat = '(?:%s)' % '|'.join([regex(k, p, tail) for (k, p) in pats])
449 if len(pat) > 20000:
441 if len(pat) > 20000:
450 raise OverflowError()
442 raise OverflowError()
451 return re.compile(pat).match
443 return re.compile(pat).match
452 except OverflowError:
444 except OverflowError:
453 # We're using a Python with a tiny regex engine and we
445 # We're using a Python with a tiny regex engine and we
454 # made it explode, so we'll divide the pattern list in two
446 # made it explode, so we'll divide the pattern list in two
455 # until it works
447 # until it works
456 l = len(pats)
448 l = len(pats)
457 if l < 2:
449 if l < 2:
458 raise
450 raise
459 a, b = matchfn(pats[:l//2], tail), matchfn(pats[l//2:], tail)
451 a, b = matchfn(pats[:l//2], tail), matchfn(pats[l//2:], tail)
460 return lambda s: a(s) or b(s)
452 return lambda s: a(s) or b(s)
461 except re.error:
453 except re.error:
462 for k, p in pats:
454 for k, p in pats:
463 try:
455 try:
464 re.compile('(?:%s)' % regex(k, p, tail))
456 re.compile('(?:%s)' % regex(k, p, tail))
465 except re.error:
457 except re.error:
466 if src:
458 if src:
467 raise Abort("%s: invalid pattern (%s): %s" %
459 raise Abort("%s: invalid pattern (%s): %s" %
468 (src, k, p))
460 (src, k, p))
469 else:
461 else:
470 raise Abort("invalid pattern (%s): %s" % (k, p))
462 raise Abort("invalid pattern (%s): %s" % (k, p))
471 raise Abort("invalid pattern")
463 raise Abort("invalid pattern")
472
464
473 def globprefix(pat):
465 def globprefix(pat):
474 '''return the non-glob prefix of a path, e.g. foo/* -> foo'''
466 '''return the non-glob prefix of a path, e.g. foo/* -> foo'''
475 root = []
467 root = []
476 for p in pat.split('/'):
468 for p in pat.split('/'):
477 if contains_glob(p): break
469 if contains_glob(p): break
478 root.append(p)
470 root.append(p)
479 return '/'.join(root) or '.'
471 return '/'.join(root) or '.'
480
472
481 def normalizepats(names, default):
473 def normalizepats(names, default):
482 pats = []
474 pats = []
483 roots = []
475 roots = []
484 anypats = False
476 anypats = False
485 for kind, name in [patkind(p, default) for p in names]:
477 for kind, name in [patkind(p, default) for p in names]:
486 if kind in ('glob', 'relpath'):
478 if kind in ('glob', 'relpath'):
487 name = canonpath(canonroot, cwd, name)
479 name = canonpath(canonroot, cwd, name)
488 elif kind in ('relglob', 'path'):
480 elif kind in ('relglob', 'path'):
489 name = normpath(name)
481 name = normpath(name)
490
482
491 pats.append((kind, name))
483 pats.append((kind, name))
492
484
493 if kind in ('glob', 're', 'relglob', 'relre'):
485 if kind in ('glob', 're', 'relglob', 'relre'):
494 anypats = True
486 anypats = True
495
487
496 if kind == 'glob':
488 if kind == 'glob':
497 root = globprefix(name)
489 root = globprefix(name)
498 roots.append(root)
490 roots.append(root)
499 elif kind in ('relpath', 'path'):
491 elif kind in ('relpath', 'path'):
500 roots.append(name or '.')
492 roots.append(name or '.')
501 elif kind == 'relglob':
493 elif kind == 'relglob':
502 roots.append('.')
494 roots.append('.')
503 return roots, pats, anypats
495 return roots, pats, anypats
504
496
505 roots, pats, anypats = normalizepats(names, dflt_pat)
497 roots, pats, anypats = normalizepats(names, dflt_pat)
506
498
507 patmatch = matchfn(pats, '$') or always
499 patmatch = matchfn(pats, '$') or always
508 incmatch = always
500 incmatch = always
509 if inc:
501 if inc:
510 dummy, inckinds, dummy = normalizepats(inc, 'glob')
502 dummy, inckinds, dummy = normalizepats(inc, 'glob')
511 incmatch = matchfn(inckinds, '(?:/|$)')
503 incmatch = matchfn(inckinds, '(?:/|$)')
512 excmatch = never
504 excmatch = never
513 if exc:
505 if exc:
514 dummy, exckinds, dummy = normalizepats(exc, 'glob')
506 dummy, exckinds, dummy = normalizepats(exc, 'glob')
515 excmatch = matchfn(exckinds, '(?:/|$)')
507 excmatch = matchfn(exckinds, '(?:/|$)')
516
508
517 if not names and inc and not exc:
509 if not names and inc and not exc:
518 # common case: hgignore patterns
510 # common case: hgignore patterns
519 match = incmatch
511 match = incmatch
520 else:
512 else:
521 match = lambda fn: incmatch(fn) and not excmatch(fn) and patmatch(fn)
513 match = lambda fn: incmatch(fn) and not excmatch(fn) and patmatch(fn)
522
514
523 return (roots, match, (inc or exc or anypats) and True)
515 return (roots, match, (inc or exc or anypats) and True)
524
516
525 _hgexecutable = None
517 _hgexecutable = None
526
518
527 def main_is_frozen():
519 def main_is_frozen():
528 """return True if we are a frozen executable.
520 """return True if we are a frozen executable.
529
521
530 The code supports py2exe (most common, Windows only) and tools/freeze
522 The code supports py2exe (most common, Windows only) and tools/freeze
531 (portable, not much used).
523 (portable, not much used).
532 """
524 """
533 return (hasattr(sys, "frozen") or # new py2exe
525 return (hasattr(sys, "frozen") or # new py2exe
534 hasattr(sys, "importers") or # old py2exe
526 hasattr(sys, "importers") or # old py2exe
535 imp.is_frozen("__main__")) # tools/freeze
527 imp.is_frozen("__main__")) # tools/freeze
536
528
537 def hgexecutable():
529 def hgexecutable():
538 """return location of the 'hg' executable.
530 """return location of the 'hg' executable.
539
531
540 Defaults to $HG or 'hg' in the search path.
532 Defaults to $HG or 'hg' in the search path.
541 """
533 """
542 if _hgexecutable is None:
534 if _hgexecutable is None:
543 hg = os.environ.get('HG')
535 hg = os.environ.get('HG')
544 if hg:
536 if hg:
545 set_hgexecutable(hg)
537 set_hgexecutable(hg)
546 elif main_is_frozen():
538 elif main_is_frozen():
547 set_hgexecutable(sys.executable)
539 set_hgexecutable(sys.executable)
548 else:
540 else:
549 set_hgexecutable(find_exe('hg') or 'hg')
541 set_hgexecutable(find_exe('hg') or 'hg')
550 return _hgexecutable
542 return _hgexecutable
551
543
552 def set_hgexecutable(path):
544 def set_hgexecutable(path):
553 """set location of the 'hg' executable"""
545 """set location of the 'hg' executable"""
554 global _hgexecutable
546 global _hgexecutable
555 _hgexecutable = path
547 _hgexecutable = path
556
548
557 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None):
549 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None):
558 '''enhanced shell command execution.
550 '''enhanced shell command execution.
559 run with environment maybe modified, maybe in different dir.
551 run with environment maybe modified, maybe in different dir.
560
552
561 if command fails and onerr is None, return status. if ui object,
553 if command fails and onerr is None, return status. if ui object,
562 print error message and return status, else raise onerr object as
554 print error message and return status, else raise onerr object as
563 exception.'''
555 exception.'''
564 def py2shell(val):
556 def py2shell(val):
565 'convert python object into string that is useful to shell'
557 'convert python object into string that is useful to shell'
566 if val in (None, False):
558 if val in (None, False):
567 return '0'
559 return '0'
568 if val == True:
560 if val == True:
569 return '1'
561 return '1'
570 return str(val)
562 return str(val)
571 oldenv = {}
563 oldenv = {}
572 for k in environ:
564 for k in environ:
573 oldenv[k] = os.environ.get(k)
565 oldenv[k] = os.environ.get(k)
574 if cwd is not None:
566 if cwd is not None:
575 oldcwd = os.getcwd()
567 oldcwd = os.getcwd()
576 origcmd = cmd
568 origcmd = cmd
577 if os.name == 'nt':
569 if os.name == 'nt':
578 cmd = '"%s"' % cmd
570 cmd = '"%s"' % cmd
579 try:
571 try:
580 for k, v in environ.iteritems():
572 for k, v in environ.iteritems():
581 os.environ[k] = py2shell(v)
573 os.environ[k] = py2shell(v)
582 os.environ['HG'] = hgexecutable()
574 os.environ['HG'] = hgexecutable()
583 if cwd is not None and oldcwd != cwd:
575 if cwd is not None and oldcwd != cwd:
584 os.chdir(cwd)
576 os.chdir(cwd)
585 rc = os.system(cmd)
577 rc = os.system(cmd)
586 if sys.platform == 'OpenVMS' and rc & 1:
578 if sys.platform == 'OpenVMS' and rc & 1:
587 rc = 0
579 rc = 0
588 if rc and onerr:
580 if rc and onerr:
589 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
581 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
590 explain_exit(rc)[0])
582 explain_exit(rc)[0])
591 if errprefix:
583 if errprefix:
592 errmsg = '%s: %s' % (errprefix, errmsg)
584 errmsg = '%s: %s' % (errprefix, errmsg)
593 try:
585 try:
594 onerr.warn(errmsg + '\n')
586 onerr.warn(errmsg + '\n')
595 except AttributeError:
587 except AttributeError:
596 raise onerr(errmsg)
588 raise onerr(errmsg)
597 return rc
589 return rc
598 finally:
590 finally:
599 for k, v in oldenv.iteritems():
591 for k, v in oldenv.iteritems():
600 if v is None:
592 if v is None:
601 del os.environ[k]
593 del os.environ[k]
602 else:
594 else:
603 os.environ[k] = v
595 os.environ[k] = v
604 if cwd is not None and oldcwd != cwd:
596 if cwd is not None and oldcwd != cwd:
605 os.chdir(oldcwd)
597 os.chdir(oldcwd)
606
598
607 def checksignature(func):
599 def checksignature(func):
608 '''wrap a function with code to check for calling errors'''
600 '''wrap a function with code to check for calling errors'''
609 def check(*args, **kwargs):
601 def check(*args, **kwargs):
610 try:
602 try:
611 return func(*args, **kwargs)
603 return func(*args, **kwargs)
612 except TypeError:
604 except TypeError:
613 if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
605 if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
614 raise error.SignatureError
606 raise error.SignatureError
615 raise
607 raise
616
608
617 return check
609 return check
618
610
619 # os.path.lexists is not available on python2.3
611 # os.path.lexists is not available on python2.3
620 def lexists(filename):
612 def lexists(filename):
621 "test whether a file with this name exists. does not follow symlinks"
613 "test whether a file with this name exists. does not follow symlinks"
622 try:
614 try:
623 os.lstat(filename)
615 os.lstat(filename)
624 except:
616 except:
625 return False
617 return False
626 return True
618 return True
627
619
628 def rename(src, dst):
620 def rename(src, dst):
629 """forcibly rename a file"""
621 """forcibly rename a file"""
630 try:
622 try:
631 os.rename(src, dst)
623 os.rename(src, dst)
632 except OSError, err: # FIXME: check err (EEXIST ?)
624 except OSError, err: # FIXME: check err (EEXIST ?)
633
625
634 # On windows, rename to existing file is not allowed, so we
626 # On windows, rename to existing file is not allowed, so we
635 # must delete destination first. But if a file is open, unlink
627 # must delete destination first. But if a file is open, unlink
636 # schedules it for delete but does not delete it. Rename
628 # schedules it for delete but does not delete it. Rename
637 # happens immediately even for open files, so we rename
629 # happens immediately even for open files, so we rename
638 # destination to a temporary name, then delete that. Then
630 # destination to a temporary name, then delete that. Then
639 # rename is safe to do.
631 # rename is safe to do.
640 # The temporary name is chosen at random to avoid the situation
632 # The temporary name is chosen at random to avoid the situation
641 # where a file is left lying around from a previous aborted run.
633 # where a file is left lying around from a previous aborted run.
642 # The usual race condition this introduces can't be avoided as
634 # The usual race condition this introduces can't be avoided as
643 # we need the name to rename into, and not the file itself. Due
635 # we need the name to rename into, and not the file itself. Due
644 # to the nature of the operation however, any races will at worst
636 # to the nature of the operation however, any races will at worst
645 # lead to the rename failing and the current operation aborting.
637 # lead to the rename failing and the current operation aborting.
646
638
647 def tempname(prefix):
639 def tempname(prefix):
648 for tries in xrange(10):
640 for tries in xrange(10):
649 temp = '%s-%08x' % (prefix, random.randint(0, 0xffffffff))
641 temp = '%s-%08x' % (prefix, random.randint(0, 0xffffffff))
650 if not os.path.exists(temp):
642 if not os.path.exists(temp):
651 return temp
643 return temp
652 raise IOError, (errno.EEXIST, "No usable temporary filename found")
644 raise IOError, (errno.EEXIST, "No usable temporary filename found")
653
645
654 temp = tempname(dst)
646 temp = tempname(dst)
655 os.rename(dst, temp)
647 os.rename(dst, temp)
656 os.unlink(temp)
648 os.unlink(temp)
657 os.rename(src, dst)
649 os.rename(src, dst)
658
650
659 def unlink(f):
651 def unlink(f):
660 """unlink and remove the directory if it is empty"""
652 """unlink and remove the directory if it is empty"""
661 os.unlink(f)
653 os.unlink(f)
662 # try removing directories that might now be empty
654 # try removing directories that might now be empty
663 try:
655 try:
664 os.removedirs(os.path.dirname(f))
656 os.removedirs(os.path.dirname(f))
665 except OSError:
657 except OSError:
666 pass
658 pass
667
659
668 def copyfile(src, dest):
660 def copyfile(src, dest):
669 "copy a file, preserving mode and atime/mtime"
661 "copy a file, preserving mode and atime/mtime"
670 if os.path.islink(src):
662 if os.path.islink(src):
671 try:
663 try:
672 os.unlink(dest)
664 os.unlink(dest)
673 except:
665 except:
674 pass
666 pass
675 os.symlink(os.readlink(src), dest)
667 os.symlink(os.readlink(src), dest)
676 else:
668 else:
677 try:
669 try:
678 shutil.copyfile(src, dest)
670 shutil.copyfile(src, dest)
679 shutil.copystat(src, dest)
671 shutil.copystat(src, dest)
680 except shutil.Error, inst:
672 except shutil.Error, inst:
681 raise Abort(str(inst))
673 raise Abort(str(inst))
682
674
683 def copyfiles(src, dst, hardlink=None):
675 def copyfiles(src, dst, hardlink=None):
684 """Copy a directory tree using hardlinks if possible"""
676 """Copy a directory tree using hardlinks if possible"""
685
677
686 if hardlink is None:
678 if hardlink is None:
687 hardlink = (os.stat(src).st_dev ==
679 hardlink = (os.stat(src).st_dev ==
688 os.stat(os.path.dirname(dst)).st_dev)
680 os.stat(os.path.dirname(dst)).st_dev)
689
681
690 if os.path.isdir(src):
682 if os.path.isdir(src):
691 os.mkdir(dst)
683 os.mkdir(dst)
692 for name, kind in osutil.listdir(src):
684 for name, kind in osutil.listdir(src):
693 srcname = os.path.join(src, name)
685 srcname = os.path.join(src, name)
694 dstname = os.path.join(dst, name)
686 dstname = os.path.join(dst, name)
695 copyfiles(srcname, dstname, hardlink)
687 copyfiles(srcname, dstname, hardlink)
696 else:
688 else:
697 if hardlink:
689 if hardlink:
698 try:
690 try:
699 os_link(src, dst)
691 os_link(src, dst)
700 except (IOError, OSError):
692 except (IOError, OSError):
701 hardlink = False
693 hardlink = False
702 shutil.copy(src, dst)
694 shutil.copy(src, dst)
703 else:
695 else:
704 shutil.copy(src, dst)
696 shutil.copy(src, dst)
705
697
706 class path_auditor(object):
698 class path_auditor(object):
707 '''ensure that a filesystem path contains no banned components.
699 '''ensure that a filesystem path contains no banned components.
708 the following properties of a path are checked:
700 the following properties of a path are checked:
709
701
710 - under top-level .hg
702 - under top-level .hg
711 - starts at the root of a windows drive
703 - starts at the root of a windows drive
712 - contains ".."
704 - contains ".."
713 - traverses a symlink (e.g. a/symlink_here/b)
705 - traverses a symlink (e.g. a/symlink_here/b)
714 - inside a nested repository'''
706 - inside a nested repository'''
715
707
716 def __init__(self, root):
708 def __init__(self, root):
717 self.audited = set()
709 self.audited = set()
718 self.auditeddir = set()
710 self.auditeddir = set()
719 self.root = root
711 self.root = root
720
712
721 def __call__(self, path):
713 def __call__(self, path):
722 if path in self.audited:
714 if path in self.audited:
723 return
715 return
724 normpath = os.path.normcase(path)
716 normpath = os.path.normcase(path)
725 parts = splitpath(normpath)
717 parts = splitpath(normpath)
726 if (os.path.splitdrive(path)[0]
718 if (os.path.splitdrive(path)[0]
727 or parts[0].lower() in ('.hg', '.hg.', '')
719 or parts[0].lower() in ('.hg', '.hg.', '')
728 or os.pardir in parts):
720 or os.pardir in parts):
729 raise Abort(_("path contains illegal component: %s") % path)
721 raise Abort(_("path contains illegal component: %s") % path)
730 if '.hg' in path.lower():
722 if '.hg' in path.lower():
731 lparts = [p.lower() for p in parts]
723 lparts = [p.lower() for p in parts]
732 for p in '.hg', '.hg.':
724 for p in '.hg', '.hg.':
733 if p in lparts[1:]:
725 if p in lparts[1:]:
734 pos = lparts.index(p)
726 pos = lparts.index(p)
735 base = os.path.join(*parts[:pos])
727 base = os.path.join(*parts[:pos])
736 raise Abort(_('path %r is inside repo %r') % (path, base))
728 raise Abort(_('path %r is inside repo %r') % (path, base))
737 def check(prefix):
729 def check(prefix):
738 curpath = os.path.join(self.root, prefix)
730 curpath = os.path.join(self.root, prefix)
739 try:
731 try:
740 st = os.lstat(curpath)
732 st = os.lstat(curpath)
741 except OSError, err:
733 except OSError, err:
742 # EINVAL can be raised as invalid path syntax under win32.
734 # EINVAL can be raised as invalid path syntax under win32.
743 # They must be ignored for patterns can be checked too.
735 # They must be ignored for patterns can be checked too.
744 if err.errno not in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):
736 if err.errno not in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):
745 raise
737 raise
746 else:
738 else:
747 if stat.S_ISLNK(st.st_mode):
739 if stat.S_ISLNK(st.st_mode):
748 raise Abort(_('path %r traverses symbolic link %r') %
740 raise Abort(_('path %r traverses symbolic link %r') %
749 (path, prefix))
741 (path, prefix))
750 elif (stat.S_ISDIR(st.st_mode) and
742 elif (stat.S_ISDIR(st.st_mode) and
751 os.path.isdir(os.path.join(curpath, '.hg'))):
743 os.path.isdir(os.path.join(curpath, '.hg'))):
752 raise Abort(_('path %r is inside repo %r') %
744 raise Abort(_('path %r is inside repo %r') %
753 (path, prefix))
745 (path, prefix))
754 parts.pop()
746 parts.pop()
755 prefixes = []
747 prefixes = []
756 for n in range(len(parts)):
748 for n in range(len(parts)):
757 prefix = os.sep.join(parts)
749 prefix = os.sep.join(parts)
758 if prefix in self.auditeddir:
750 if prefix in self.auditeddir:
759 break
751 break
760 check(prefix)
752 check(prefix)
761 prefixes.append(prefix)
753 prefixes.append(prefix)
762 parts.pop()
754 parts.pop()
763
755
764 self.audited.add(path)
756 self.audited.add(path)
765 # only add prefixes to the cache after checking everything: we don't
757 # only add prefixes to the cache after checking everything: we don't
766 # want to add "foo/bar/baz" before checking if there's a "foo/.hg"
758 # want to add "foo/bar/baz" before checking if there's a "foo/.hg"
767 self.auditeddir.update(prefixes)
759 self.auditeddir.update(prefixes)
768
760
769 def nlinks(pathname):
761 def nlinks(pathname):
770 """Return number of hardlinks for the given file."""
762 """Return number of hardlinks for the given file."""
771 return os.lstat(pathname).st_nlink
763 return os.lstat(pathname).st_nlink
772
764
773 if hasattr(os, 'link'):
765 if hasattr(os, 'link'):
774 os_link = os.link
766 os_link = os.link
775 else:
767 else:
776 def os_link(src, dst):
768 def os_link(src, dst):
777 raise OSError(0, _("Hardlinks not supported"))
769 raise OSError(0, _("Hardlinks not supported"))
778
770
779 def lookup_reg(key, name=None, scope=None):
771 def lookup_reg(key, name=None, scope=None):
780 return None
772 return None
781
773
782 if os.name == 'nt':
774 if os.name == 'nt':
783 from windows import *
775 from windows import *
784 def expand_glob(pats):
776 def expand_glob(pats):
785 '''On Windows, expand the implicit globs in a list of patterns'''
777 '''On Windows, expand the implicit globs in a list of patterns'''
786 ret = []
778 ret = []
787 for p in pats:
779 for p in pats:
788 kind, name = patkind(p, None)
780 kind, name = patkind(p, None)
789 if kind is None:
781 if kind is None:
790 globbed = glob.glob(name)
782 globbed = glob.glob(name)
791 if globbed:
783 if globbed:
792 ret.extend(globbed)
784 ret.extend(globbed)
793 continue
785 continue
794 # if we couldn't expand the glob, just keep it around
786 # if we couldn't expand the glob, just keep it around
795 ret.append(p)
787 ret.append(p)
796 return ret
788 return ret
797 else:
789 else:
798 from posix import *
790 from posix import *
799
791
800 def makelock(info, pathname):
792 def makelock(info, pathname):
801 try:
793 try:
802 return os.symlink(info, pathname)
794 return os.symlink(info, pathname)
803 except OSError, why:
795 except OSError, why:
804 if why.errno == errno.EEXIST:
796 if why.errno == errno.EEXIST:
805 raise
797 raise
806 except AttributeError: # no symlink in os
798 except AttributeError: # no symlink in os
807 pass
799 pass
808
800
809 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
801 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
810 os.write(ld, info)
802 os.write(ld, info)
811 os.close(ld)
803 os.close(ld)
812
804
813 def readlock(pathname):
805 def readlock(pathname):
814 try:
806 try:
815 return os.readlink(pathname)
807 return os.readlink(pathname)
816 except OSError, why:
808 except OSError, why:
817 if why.errno not in (errno.EINVAL, errno.ENOSYS):
809 if why.errno not in (errno.EINVAL, errno.ENOSYS):
818 raise
810 raise
819 except AttributeError: # no symlink in os
811 except AttributeError: # no symlink in os
820 pass
812 pass
821 return posixfile(pathname).read()
813 return posixfile(pathname).read()
822
814
823 def fstat(fp):
815 def fstat(fp):
824 '''stat file object that may not have fileno method.'''
816 '''stat file object that may not have fileno method.'''
825 try:
817 try:
826 return os.fstat(fp.fileno())
818 return os.fstat(fp.fileno())
827 except AttributeError:
819 except AttributeError:
828 return os.stat(fp.name)
820 return os.stat(fp.name)
829
821
830 # File system features
822 # File system features
831
823
832 def checkcase(path):
824 def checkcase(path):
833 """
825 """
834 Check whether the given path is on a case-sensitive filesystem
826 Check whether the given path is on a case-sensitive filesystem
835
827
836 Requires a path (like /foo/.hg) ending with a foldable final
828 Requires a path (like /foo/.hg) ending with a foldable final
837 directory component.
829 directory component.
838 """
830 """
839 s1 = os.stat(path)
831 s1 = os.stat(path)
840 d, b = os.path.split(path)
832 d, b = os.path.split(path)
841 p2 = os.path.join(d, b.upper())
833 p2 = os.path.join(d, b.upper())
842 if path == p2:
834 if path == p2:
843 p2 = os.path.join(d, b.lower())
835 p2 = os.path.join(d, b.lower())
844 try:
836 try:
845 s2 = os.stat(p2)
837 s2 = os.stat(p2)
846 if s2 == s1:
838 if s2 == s1:
847 return False
839 return False
848 return True
840 return True
849 except:
841 except:
850 return True
842 return True
851
843
852 _fspathcache = {}
844 _fspathcache = {}
853 def fspath(name, root):
845 def fspath(name, root):
854 '''Get name in the case stored in the filesystem
846 '''Get name in the case stored in the filesystem
855
847
856 The name is either relative to root, or it is an absolute path starting
848 The name is either relative to root, or it is an absolute path starting
857 with root. Note that this function is unnecessary, and should not be
849 with root. Note that this function is unnecessary, and should not be
858 called, for case-sensitive filesystems (simply because it's expensive).
850 called, for case-sensitive filesystems (simply because it's expensive).
859 '''
851 '''
860 # If name is absolute, make it relative
852 # If name is absolute, make it relative
861 if name.lower().startswith(root.lower()):
853 if name.lower().startswith(root.lower()):
862 l = len(root)
854 l = len(root)
863 if name[l] == os.sep or name[l] == os.altsep:
855 if name[l] == os.sep or name[l] == os.altsep:
864 l = l + 1
856 l = l + 1
865 name = name[l:]
857 name = name[l:]
866
858
867 if not os.path.exists(os.path.join(root, name)):
859 if not os.path.exists(os.path.join(root, name)):
868 return None
860 return None
869
861
870 seps = os.sep
862 seps = os.sep
871 if os.altsep:
863 if os.altsep:
872 seps = seps + os.altsep
864 seps = seps + os.altsep
873 # Protect backslashes. This gets silly very quickly.
865 # Protect backslashes. This gets silly very quickly.
874 seps.replace('\\','\\\\')
866 seps.replace('\\','\\\\')
875 pattern = re.compile(r'([^%s]+)|([%s]+)' % (seps, seps))
867 pattern = re.compile(r'([^%s]+)|([%s]+)' % (seps, seps))
876 dir = os.path.normcase(os.path.normpath(root))
868 dir = os.path.normcase(os.path.normpath(root))
877 result = []
869 result = []
878 for part, sep in pattern.findall(name):
870 for part, sep in pattern.findall(name):
879 if sep:
871 if sep:
880 result.append(sep)
872 result.append(sep)
881 continue
873 continue
882
874
883 if dir not in _fspathcache:
875 if dir not in _fspathcache:
884 _fspathcache[dir] = os.listdir(dir)
876 _fspathcache[dir] = os.listdir(dir)
885 contents = _fspathcache[dir]
877 contents = _fspathcache[dir]
886
878
887 lpart = part.lower()
879 lpart = part.lower()
888 for n in contents:
880 for n in contents:
889 if n.lower() == lpart:
881 if n.lower() == lpart:
890 result.append(n)
882 result.append(n)
891 break
883 break
892 else:
884 else:
893 # Cannot happen, as the file exists!
885 # Cannot happen, as the file exists!
894 result.append(part)
886 result.append(part)
895 dir = os.path.join(dir, lpart)
887 dir = os.path.join(dir, lpart)
896
888
897 return ''.join(result)
889 return ''.join(result)
898
890
899 def checkexec(path):
891 def checkexec(path):
900 """
892 """
901 Check whether the given path is on a filesystem with UNIX-like exec flags
893 Check whether the given path is on a filesystem with UNIX-like exec flags
902
894
903 Requires a directory (like /foo/.hg)
895 Requires a directory (like /foo/.hg)
904 """
896 """
905
897
906 # VFAT on some Linux versions can flip mode but it doesn't persist
898 # VFAT on some Linux versions can flip mode but it doesn't persist
907 # a FS remount. Frequently we can detect it if files are created
899 # a FS remount. Frequently we can detect it if files are created
908 # with exec bit on.
900 # with exec bit on.
909
901
910 try:
902 try:
911 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
903 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
912 fh, fn = tempfile.mkstemp("", "", path)
904 fh, fn = tempfile.mkstemp("", "", path)
913 try:
905 try:
914 os.close(fh)
906 os.close(fh)
915 m = os.stat(fn).st_mode & 0777
907 m = os.stat(fn).st_mode & 0777
916 new_file_has_exec = m & EXECFLAGS
908 new_file_has_exec = m & EXECFLAGS
917 os.chmod(fn, m ^ EXECFLAGS)
909 os.chmod(fn, m ^ EXECFLAGS)
918 exec_flags_cannot_flip = ((os.stat(fn).st_mode & 0777) == m)
910 exec_flags_cannot_flip = ((os.stat(fn).st_mode & 0777) == m)
919 finally:
911 finally:
920 os.unlink(fn)
912 os.unlink(fn)
921 except (IOError, OSError):
913 except (IOError, OSError):
922 # we don't care, the user probably won't be able to commit anyway
914 # we don't care, the user probably won't be able to commit anyway
923 return False
915 return False
924 return not (new_file_has_exec or exec_flags_cannot_flip)
916 return not (new_file_has_exec or exec_flags_cannot_flip)
925
917
926 def checklink(path):
918 def checklink(path):
927 """check whether the given path is on a symlink-capable filesystem"""
919 """check whether the given path is on a symlink-capable filesystem"""
928 # mktemp is not racy because symlink creation will fail if the
920 # mktemp is not racy because symlink creation will fail if the
929 # file already exists
921 # file already exists
930 name = tempfile.mktemp(dir=path)
922 name = tempfile.mktemp(dir=path)
931 try:
923 try:
932 os.symlink(".", name)
924 os.symlink(".", name)
933 os.unlink(name)
925 os.unlink(name)
934 return True
926 return True
935 except (OSError, AttributeError):
927 except (OSError, AttributeError):
936 return False
928 return False
937
929
938 def needbinarypatch():
930 def needbinarypatch():
939 """return True if patches should be applied in binary mode by default."""
931 """return True if patches should be applied in binary mode by default."""
940 return os.name == 'nt'
932 return os.name == 'nt'
941
933
942 def endswithsep(path):
934 def endswithsep(path):
943 '''Check path ends with os.sep or os.altsep.'''
935 '''Check path ends with os.sep or os.altsep.'''
944 return path.endswith(os.sep) or os.altsep and path.endswith(os.altsep)
936 return path.endswith(os.sep) or os.altsep and path.endswith(os.altsep)
945
937
946 def splitpath(path):
938 def splitpath(path):
947 '''Split path by os.sep.
939 '''Split path by os.sep.
948 Note that this function does not use os.altsep because this is
940 Note that this function does not use os.altsep because this is
949 an alternative of simple "xxx.split(os.sep)".
941 an alternative of simple "xxx.split(os.sep)".
950 It is recommended to use os.path.normpath() before using this
942 It is recommended to use os.path.normpath() before using this
951 function if need.'''
943 function if need.'''
952 return path.split(os.sep)
944 return path.split(os.sep)
953
945
954 def gui():
946 def gui():
955 '''Are we running in a GUI?'''
947 '''Are we running in a GUI?'''
956 return os.name == "nt" or os.name == "mac" or os.environ.get("DISPLAY")
948 return os.name == "nt" or os.name == "mac" or os.environ.get("DISPLAY")
957
949
958 def mktempcopy(name, emptyok=False, createmode=None):
950 def mktempcopy(name, emptyok=False, createmode=None):
959 """Create a temporary file with the same contents from name
951 """Create a temporary file with the same contents from name
960
952
961 The permission bits are copied from the original file.
953 The permission bits are copied from the original file.
962
954
963 If the temporary file is going to be truncated immediately, you
955 If the temporary file is going to be truncated immediately, you
964 can use emptyok=True as an optimization.
956 can use emptyok=True as an optimization.
965
957
966 Returns the name of the temporary file.
958 Returns the name of the temporary file.
967 """
959 """
968 d, fn = os.path.split(name)
960 d, fn = os.path.split(name)
969 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
961 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
970 os.close(fd)
962 os.close(fd)
971 # Temporary files are created with mode 0600, which is usually not
963 # Temporary files are created with mode 0600, which is usually not
972 # what we want. If the original file already exists, just copy
964 # what we want. If the original file already exists, just copy
973 # its mode. Otherwise, manually obey umask.
965 # its mode. Otherwise, manually obey umask.
974 try:
966 try:
975 st_mode = os.lstat(name).st_mode & 0777
967 st_mode = os.lstat(name).st_mode & 0777
976 except OSError, inst:
968 except OSError, inst:
977 if inst.errno != errno.ENOENT:
969 if inst.errno != errno.ENOENT:
978 raise
970 raise
979 st_mode = createmode
971 st_mode = createmode
980 if st_mode is None:
972 if st_mode is None:
981 st_mode = ~umask
973 st_mode = ~umask
982 st_mode &= 0666
974 st_mode &= 0666
983 os.chmod(temp, st_mode)
975 os.chmod(temp, st_mode)
984 if emptyok:
976 if emptyok:
985 return temp
977 return temp
986 try:
978 try:
987 try:
979 try:
988 ifp = posixfile(name, "rb")
980 ifp = posixfile(name, "rb")
989 except IOError, inst:
981 except IOError, inst:
990 if inst.errno == errno.ENOENT:
982 if inst.errno == errno.ENOENT:
991 return temp
983 return temp
992 if not getattr(inst, 'filename', None):
984 if not getattr(inst, 'filename', None):
993 inst.filename = name
985 inst.filename = name
994 raise
986 raise
995 ofp = posixfile(temp, "wb")
987 ofp = posixfile(temp, "wb")
996 for chunk in filechunkiter(ifp):
988 for chunk in filechunkiter(ifp):
997 ofp.write(chunk)
989 ofp.write(chunk)
998 ifp.close()
990 ifp.close()
999 ofp.close()
991 ofp.close()
1000 except:
992 except:
1001 try: os.unlink(temp)
993 try: os.unlink(temp)
1002 except: pass
994 except: pass
1003 raise
995 raise
1004 return temp
996 return temp
1005
997
1006 class atomictempfile(posixfile):
998 class atomictempfile(posixfile):
1007 """file-like object that atomically updates a file
999 """file-like object that atomically updates a file
1008
1000
1009 All writes will be redirected to a temporary copy of the original
1001 All writes will be redirected to a temporary copy of the original
1010 file. When rename is called, the copy is renamed to the original
1002 file. When rename is called, the copy is renamed to the original
1011 name, making the changes visible.
1003 name, making the changes visible.
1012 """
1004 """
1013 def __init__(self, name, mode, createmode):
1005 def __init__(self, name, mode, createmode):
1014 self.__name = name
1006 self.__name = name
1015 self.temp = mktempcopy(name, emptyok=('w' in mode),
1007 self.temp = mktempcopy(name, emptyok=('w' in mode),
1016 createmode=createmode)
1008 createmode=createmode)
1017 posixfile.__init__(self, self.temp, mode)
1009 posixfile.__init__(self, self.temp, mode)
1018
1010
1019 def rename(self):
1011 def rename(self):
1020 if not self.closed:
1012 if not self.closed:
1021 posixfile.close(self)
1013 posixfile.close(self)
1022 rename(self.temp, localpath(self.__name))
1014 rename(self.temp, localpath(self.__name))
1023
1015
1024 def __del__(self):
1016 def __del__(self):
1025 if not self.closed:
1017 if not self.closed:
1026 try:
1018 try:
1027 os.unlink(self.temp)
1019 os.unlink(self.temp)
1028 except: pass
1020 except: pass
1029 posixfile.close(self)
1021 posixfile.close(self)
1030
1022
1031 def makedirs(name, mode=None):
1023 def makedirs(name, mode=None):
1032 """recursive directory creation with parent mode inheritance"""
1024 """recursive directory creation with parent mode inheritance"""
1033 try:
1025 try:
1034 os.mkdir(name)
1026 os.mkdir(name)
1035 if mode is not None:
1027 if mode is not None:
1036 os.chmod(name, mode)
1028 os.chmod(name, mode)
1037 return
1029 return
1038 except OSError, err:
1030 except OSError, err:
1039 if err.errno == errno.EEXIST:
1031 if err.errno == errno.EEXIST:
1040 return
1032 return
1041 if err.errno != errno.ENOENT:
1033 if err.errno != errno.ENOENT:
1042 raise
1034 raise
1043 parent = os.path.abspath(os.path.dirname(name))
1035 parent = os.path.abspath(os.path.dirname(name))
1044 makedirs(parent, mode)
1036 makedirs(parent, mode)
1045 makedirs(name, mode)
1037 makedirs(name, mode)
1046
1038
1047 class opener(object):
1039 class opener(object):
1048 """Open files relative to a base directory
1040 """Open files relative to a base directory
1049
1041
1050 This class is used to hide the details of COW semantics and
1042 This class is used to hide the details of COW semantics and
1051 remote file access from higher level code.
1043 remote file access from higher level code.
1052 """
1044 """
1053 def __init__(self, base, audit=True):
1045 def __init__(self, base, audit=True):
1054 self.base = base
1046 self.base = base
1055 if audit:
1047 if audit:
1056 self.audit_path = path_auditor(base)
1048 self.audit_path = path_auditor(base)
1057 else:
1049 else:
1058 self.audit_path = always
1050 self.audit_path = always
1059 self.createmode = None
1051 self.createmode = None
1060
1052
1061 def __getattr__(self, name):
1053 def __getattr__(self, name):
1062 if name == '_can_symlink':
1054 if name == '_can_symlink':
1063 self._can_symlink = checklink(self.base)
1055 self._can_symlink = checklink(self.base)
1064 return self._can_symlink
1056 return self._can_symlink
1065 raise AttributeError(name)
1057 raise AttributeError(name)
1066
1058
1067 def _fixfilemode(self, name):
1059 def _fixfilemode(self, name):
1068 if self.createmode is None:
1060 if self.createmode is None:
1069 return
1061 return
1070 os.chmod(name, self.createmode & 0666)
1062 os.chmod(name, self.createmode & 0666)
1071
1063
1072 def __call__(self, path, mode="r", text=False, atomictemp=False):
1064 def __call__(self, path, mode="r", text=False, atomictemp=False):
1073 self.audit_path(path)
1065 self.audit_path(path)
1074 f = os.path.join(self.base, path)
1066 f = os.path.join(self.base, path)
1075
1067
1076 if not text and "b" not in mode:
1068 if not text and "b" not in mode:
1077 mode += "b" # for that other OS
1069 mode += "b" # for that other OS
1078
1070
1079 nlink = -1
1071 nlink = -1
1080 if mode not in ("r", "rb"):
1072 if mode not in ("r", "rb"):
1081 try:
1073 try:
1082 nlink = nlinks(f)
1074 nlink = nlinks(f)
1083 except OSError:
1075 except OSError:
1084 nlink = 0
1076 nlink = 0
1085 d = os.path.dirname(f)
1077 d = os.path.dirname(f)
1086 if not os.path.isdir(d):
1078 if not os.path.isdir(d):
1087 makedirs(d, self.createmode)
1079 makedirs(d, self.createmode)
1088 if atomictemp:
1080 if atomictemp:
1089 return atomictempfile(f, mode, self.createmode)
1081 return atomictempfile(f, mode, self.createmode)
1090 if nlink > 1:
1082 if nlink > 1:
1091 rename(mktempcopy(f), f)
1083 rename(mktempcopy(f), f)
1092 fp = posixfile(f, mode)
1084 fp = posixfile(f, mode)
1093 if nlink == 0:
1085 if nlink == 0:
1094 self._fixfilemode(f)
1086 self._fixfilemode(f)
1095 return fp
1087 return fp
1096
1088
1097 def symlink(self, src, dst):
1089 def symlink(self, src, dst):
1098 self.audit_path(dst)
1090 self.audit_path(dst)
1099 linkname = os.path.join(self.base, dst)
1091 linkname = os.path.join(self.base, dst)
1100 try:
1092 try:
1101 os.unlink(linkname)
1093 os.unlink(linkname)
1102 except OSError:
1094 except OSError:
1103 pass
1095 pass
1104
1096
1105 dirname = os.path.dirname(linkname)
1097 dirname = os.path.dirname(linkname)
1106 if not os.path.exists(dirname):
1098 if not os.path.exists(dirname):
1107 makedirs(dirname, self.createmode)
1099 makedirs(dirname, self.createmode)
1108
1100
1109 if self._can_symlink:
1101 if self._can_symlink:
1110 try:
1102 try:
1111 os.symlink(src, linkname)
1103 os.symlink(src, linkname)
1112 except OSError, err:
1104 except OSError, err:
1113 raise OSError(err.errno, _('could not symlink to %r: %s') %
1105 raise OSError(err.errno, _('could not symlink to %r: %s') %
1114 (src, err.strerror), linkname)
1106 (src, err.strerror), linkname)
1115 else:
1107 else:
1116 f = self(dst, "w")
1108 f = self(dst, "w")
1117 f.write(src)
1109 f.write(src)
1118 f.close()
1110 f.close()
1119 self._fixfilemode(dst)
1111 self._fixfilemode(dst)
1120
1112
1121 class chunkbuffer(object):
1113 class chunkbuffer(object):
1122 """Allow arbitrary sized chunks of data to be efficiently read from an
1114 """Allow arbitrary sized chunks of data to be efficiently read from an
1123 iterator over chunks of arbitrary size."""
1115 iterator over chunks of arbitrary size."""
1124
1116
1125 def __init__(self, in_iter):
1117 def __init__(self, in_iter):
1126 """in_iter is the iterator that's iterating over the input chunks.
1118 """in_iter is the iterator that's iterating over the input chunks.
1127 targetsize is how big a buffer to try to maintain."""
1119 targetsize is how big a buffer to try to maintain."""
1128 self.iter = iter(in_iter)
1120 self.iter = iter(in_iter)
1129 self.buf = ''
1121 self.buf = ''
1130 self.targetsize = 2**16
1122 self.targetsize = 2**16
1131
1123
1132 def read(self, l):
1124 def read(self, l):
1133 """Read L bytes of data from the iterator of chunks of data.
1125 """Read L bytes of data from the iterator of chunks of data.
1134 Returns less than L bytes if the iterator runs dry."""
1126 Returns less than L bytes if the iterator runs dry."""
1135 if l > len(self.buf) and self.iter:
1127 if l > len(self.buf) and self.iter:
1136 # Clamp to a multiple of self.targetsize
1128 # Clamp to a multiple of self.targetsize
1137 targetsize = max(l, self.targetsize)
1129 targetsize = max(l, self.targetsize)
1138 collector = cStringIO.StringIO()
1130 collector = cStringIO.StringIO()
1139 collector.write(self.buf)
1131 collector.write(self.buf)
1140 collected = len(self.buf)
1132 collected = len(self.buf)
1141 for chunk in self.iter:
1133 for chunk in self.iter:
1142 collector.write(chunk)
1134 collector.write(chunk)
1143 collected += len(chunk)
1135 collected += len(chunk)
1144 if collected >= targetsize:
1136 if collected >= targetsize:
1145 break
1137 break
1146 if collected < targetsize:
1138 if collected < targetsize:
1147 self.iter = False
1139 self.iter = False
1148 self.buf = collector.getvalue()
1140 self.buf = collector.getvalue()
1149 if len(self.buf) == l:
1141 if len(self.buf) == l:
1150 s, self.buf = str(self.buf), ''
1142 s, self.buf = str(self.buf), ''
1151 else:
1143 else:
1152 s, self.buf = self.buf[:l], buffer(self.buf, l)
1144 s, self.buf = self.buf[:l], buffer(self.buf, l)
1153 return s
1145 return s
1154
1146
1155 def filechunkiter(f, size=65536, limit=None):
1147 def filechunkiter(f, size=65536, limit=None):
1156 """Create a generator that produces the data in the file size
1148 """Create a generator that produces the data in the file size
1157 (default 65536) bytes at a time, up to optional limit (default is
1149 (default 65536) bytes at a time, up to optional limit (default is
1158 to read all data). Chunks may be less than size bytes if the
1150 to read all data). Chunks may be less than size bytes if the
1159 chunk is the last chunk in the file, or the file is a socket or
1151 chunk is the last chunk in the file, or the file is a socket or
1160 some other type of file that sometimes reads less data than is
1152 some other type of file that sometimes reads less data than is
1161 requested."""
1153 requested."""
1162 assert size >= 0
1154 assert size >= 0
1163 assert limit is None or limit >= 0
1155 assert limit is None or limit >= 0
1164 while True:
1156 while True:
1165 if limit is None: nbytes = size
1157 if limit is None: nbytes = size
1166 else: nbytes = min(limit, size)
1158 else: nbytes = min(limit, size)
1167 s = nbytes and f.read(nbytes)
1159 s = nbytes and f.read(nbytes)
1168 if not s: break
1160 if not s: break
1169 if limit: limit -= len(s)
1161 if limit: limit -= len(s)
1170 yield s
1162 yield s
1171
1163
1172 def makedate():
1164 def makedate():
1173 lt = time.localtime()
1165 lt = time.localtime()
1174 if lt[8] == 1 and time.daylight:
1166 if lt[8] == 1 and time.daylight:
1175 tz = time.altzone
1167 tz = time.altzone
1176 else:
1168 else:
1177 tz = time.timezone
1169 tz = time.timezone
1178 return time.mktime(lt), tz
1170 return time.mktime(lt), tz
1179
1171
1180 def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
1172 def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
1181 """represent a (unixtime, offset) tuple as a localized time.
1173 """represent a (unixtime, offset) tuple as a localized time.
1182 unixtime is seconds since the epoch, and offset is the time zone's
1174 unixtime is seconds since the epoch, and offset is the time zone's
1183 number of seconds away from UTC. if timezone is false, do not
1175 number of seconds away from UTC. if timezone is false, do not
1184 append time zone to string."""
1176 append time zone to string."""
1185 t, tz = date or makedate()
1177 t, tz = date or makedate()
1186 if "%1" in format or "%2" in format:
1178 if "%1" in format or "%2" in format:
1187 sign = (tz > 0) and "-" or "+"
1179 sign = (tz > 0) and "-" or "+"
1188 minutes = abs(tz) / 60
1180 minutes = abs(tz) / 60
1189 format = format.replace("%1", "%c%02d" % (sign, minutes / 60))
1181 format = format.replace("%1", "%c%02d" % (sign, minutes / 60))
1190 format = format.replace("%2", "%02d" % (minutes % 60))
1182 format = format.replace("%2", "%02d" % (minutes % 60))
1191 s = time.strftime(format, time.gmtime(float(t) - tz))
1183 s = time.strftime(format, time.gmtime(float(t) - tz))
1192 return s
1184 return s
1193
1185
1194 def shortdate(date=None):
1186 def shortdate(date=None):
1195 """turn (timestamp, tzoff) tuple into iso 8631 date."""
1187 """turn (timestamp, tzoff) tuple into iso 8631 date."""
1196 return datestr(date, format='%Y-%m-%d')
1188 return datestr(date, format='%Y-%m-%d')
1197
1189
1198 def strdate(string, format, defaults=[]):
1190 def strdate(string, format, defaults=[]):
1199 """parse a localized time string and return a (unixtime, offset) tuple.
1191 """parse a localized time string and return a (unixtime, offset) tuple.
1200 if the string cannot be parsed, ValueError is raised."""
1192 if the string cannot be parsed, ValueError is raised."""
1201 def timezone(string):
1193 def timezone(string):
1202 tz = string.split()[-1]
1194 tz = string.split()[-1]
1203 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
1195 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
1204 sign = (tz[0] == "+") and 1 or -1
1196 sign = (tz[0] == "+") and 1 or -1
1205 hours = int(tz[1:3])
1197 hours = int(tz[1:3])
1206 minutes = int(tz[3:5])
1198 minutes = int(tz[3:5])
1207 return -sign * (hours * 60 + minutes) * 60
1199 return -sign * (hours * 60 + minutes) * 60
1208 if tz == "GMT" or tz == "UTC":
1200 if tz == "GMT" or tz == "UTC":
1209 return 0
1201 return 0
1210 return None
1202 return None
1211
1203
1212 # NOTE: unixtime = localunixtime + offset
1204 # NOTE: unixtime = localunixtime + offset
1213 offset, date = timezone(string), string
1205 offset, date = timezone(string), string
1214 if offset != None:
1206 if offset != None:
1215 date = " ".join(string.split()[:-1])
1207 date = " ".join(string.split()[:-1])
1216
1208
1217 # add missing elements from defaults
1209 # add missing elements from defaults
1218 for part in defaults:
1210 for part in defaults:
1219 found = [True for p in part if ("%"+p) in format]
1211 found = [True for p in part if ("%"+p) in format]
1220 if not found:
1212 if not found:
1221 date += "@" + defaults[part]
1213 date += "@" + defaults[part]
1222 format += "@%" + part[0]
1214 format += "@%" + part[0]
1223
1215
1224 timetuple = time.strptime(date, format)
1216 timetuple = time.strptime(date, format)
1225 localunixtime = int(calendar.timegm(timetuple))
1217 localunixtime = int(calendar.timegm(timetuple))
1226 if offset is None:
1218 if offset is None:
1227 # local timezone
1219 # local timezone
1228 unixtime = int(time.mktime(timetuple))
1220 unixtime = int(time.mktime(timetuple))
1229 offset = unixtime - localunixtime
1221 offset = unixtime - localunixtime
1230 else:
1222 else:
1231 unixtime = localunixtime + offset
1223 unixtime = localunixtime + offset
1232 return unixtime, offset
1224 return unixtime, offset
1233
1225
1234 def parsedate(date, formats=None, defaults=None):
1226 def parsedate(date, formats=None, defaults=None):
1235 """parse a localized date/time string and return a (unixtime, offset) tuple.
1227 """parse a localized date/time string and return a (unixtime, offset) tuple.
1236
1228
1237 The date may be a "unixtime offset" string or in one of the specified
1229 The date may be a "unixtime offset" string or in one of the specified
1238 formats. If the date already is a (unixtime, offset) tuple, it is returned.
1230 formats. If the date already is a (unixtime, offset) tuple, it is returned.
1239 """
1231 """
1240 if not date:
1232 if not date:
1241 return 0, 0
1233 return 0, 0
1242 if isinstance(date, tuple) and len(date) == 2:
1234 if isinstance(date, tuple) and len(date) == 2:
1243 return date
1235 return date
1244 if not formats:
1236 if not formats:
1245 formats = defaultdateformats
1237 formats = defaultdateformats
1246 date = date.strip()
1238 date = date.strip()
1247 try:
1239 try:
1248 when, offset = map(int, date.split(' '))
1240 when, offset = map(int, date.split(' '))
1249 except ValueError:
1241 except ValueError:
1250 # fill out defaults
1242 # fill out defaults
1251 if not defaults:
1243 if not defaults:
1252 defaults = {}
1244 defaults = {}
1253 now = makedate()
1245 now = makedate()
1254 for part in "d mb yY HI M S".split():
1246 for part in "d mb yY HI M S".split():
1255 if part not in defaults:
1247 if part not in defaults:
1256 if part[0] in "HMS":
1248 if part[0] in "HMS":
1257 defaults[part] = "00"
1249 defaults[part] = "00"
1258 else:
1250 else:
1259 defaults[part] = datestr(now, "%" + part[0])
1251 defaults[part] = datestr(now, "%" + part[0])
1260
1252
1261 for format in formats:
1253 for format in formats:
1262 try:
1254 try:
1263 when, offset = strdate(date, format, defaults)
1255 when, offset = strdate(date, format, defaults)
1264 except (ValueError, OverflowError):
1256 except (ValueError, OverflowError):
1265 pass
1257 pass
1266 else:
1258 else:
1267 break
1259 break
1268 else:
1260 else:
1269 raise Abort(_('invalid date: %r ') % date)
1261 raise Abort(_('invalid date: %r ') % date)
1270 # validate explicit (probably user-specified) date and
1262 # validate explicit (probably user-specified) date and
1271 # time zone offset. values must fit in signed 32 bits for
1263 # time zone offset. values must fit in signed 32 bits for
1272 # current 32-bit linux runtimes. timezones go from UTC-12
1264 # current 32-bit linux runtimes. timezones go from UTC-12
1273 # to UTC+14
1265 # to UTC+14
1274 if abs(when) > 0x7fffffff:
1266 if abs(when) > 0x7fffffff:
1275 raise Abort(_('date exceeds 32 bits: %d') % when)
1267 raise Abort(_('date exceeds 32 bits: %d') % when)
1276 if offset < -50400 or offset > 43200:
1268 if offset < -50400 or offset > 43200:
1277 raise Abort(_('impossible time zone offset: %d') % offset)
1269 raise Abort(_('impossible time zone offset: %d') % offset)
1278 return when, offset
1270 return when, offset
1279
1271
1280 def matchdate(date):
1272 def matchdate(date):
1281 """Return a function that matches a given date match specifier
1273 """Return a function that matches a given date match specifier
1282
1274
1283 Formats include:
1275 Formats include:
1284
1276
1285 '{date}' match a given date to the accuracy provided
1277 '{date}' match a given date to the accuracy provided
1286
1278
1287 '<{date}' on or before a given date
1279 '<{date}' on or before a given date
1288
1280
1289 '>{date}' on or after a given date
1281 '>{date}' on or after a given date
1290
1282
1291 """
1283 """
1292
1284
1293 def lower(date):
1285 def lower(date):
1294 d = dict(mb="1", d="1")
1286 d = dict(mb="1", d="1")
1295 return parsedate(date, extendeddateformats, d)[0]
1287 return parsedate(date, extendeddateformats, d)[0]
1296
1288
1297 def upper(date):
1289 def upper(date):
1298 d = dict(mb="12", HI="23", M="59", S="59")
1290 d = dict(mb="12", HI="23", M="59", S="59")
1299 for days in "31 30 29".split():
1291 for days in "31 30 29".split():
1300 try:
1292 try:
1301 d["d"] = days
1293 d["d"] = days
1302 return parsedate(date, extendeddateformats, d)[0]
1294 return parsedate(date, extendeddateformats, d)[0]
1303 except:
1295 except:
1304 pass
1296 pass
1305 d["d"] = "28"
1297 d["d"] = "28"
1306 return parsedate(date, extendeddateformats, d)[0]
1298 return parsedate(date, extendeddateformats, d)[0]
1307
1299
1308 date = date.strip()
1300 date = date.strip()
1309 if date[0] == "<":
1301 if date[0] == "<":
1310 when = upper(date[1:])
1302 when = upper(date[1:])
1311 return lambda x: x <= when
1303 return lambda x: x <= when
1312 elif date[0] == ">":
1304 elif date[0] == ">":
1313 when = lower(date[1:])
1305 when = lower(date[1:])
1314 return lambda x: x >= when
1306 return lambda x: x >= when
1315 elif date[0] == "-":
1307 elif date[0] == "-":
1316 try:
1308 try:
1317 days = int(date[1:])
1309 days = int(date[1:])
1318 except ValueError:
1310 except ValueError:
1319 raise Abort(_("invalid day spec: %s") % date[1:])
1311 raise Abort(_("invalid day spec: %s") % date[1:])
1320 when = makedate()[0] - days * 3600 * 24
1312 when = makedate()[0] - days * 3600 * 24
1321 return lambda x: x >= when
1313 return lambda x: x >= when
1322 elif " to " in date:
1314 elif " to " in date:
1323 a, b = date.split(" to ")
1315 a, b = date.split(" to ")
1324 start, stop = lower(a), upper(b)
1316 start, stop = lower(a), upper(b)
1325 return lambda x: x >= start and x <= stop
1317 return lambda x: x >= start and x <= stop
1326 else:
1318 else:
1327 start, stop = lower(date), upper(date)
1319 start, stop = lower(date), upper(date)
1328 return lambda x: x >= start and x <= stop
1320 return lambda x: x >= start and x <= stop
1329
1321
1330 def shortuser(user):
1322 def shortuser(user):
1331 """Return a short representation of a user name or email address."""
1323 """Return a short representation of a user name or email address."""
1332 f = user.find('@')
1324 f = user.find('@')
1333 if f >= 0:
1325 if f >= 0:
1334 user = user[:f]
1326 user = user[:f]
1335 f = user.find('<')
1327 f = user.find('<')
1336 if f >= 0:
1328 if f >= 0:
1337 user = user[f+1:]
1329 user = user[f+1:]
1338 f = user.find(' ')
1330 f = user.find(' ')
1339 if f >= 0:
1331 if f >= 0:
1340 user = user[:f]
1332 user = user[:f]
1341 f = user.find('.')
1333 f = user.find('.')
1342 if f >= 0:
1334 if f >= 0:
1343 user = user[:f]
1335 user = user[:f]
1344 return user
1336 return user
1345
1337
1346 def email(author):
1338 def email(author):
1347 '''get email of author.'''
1339 '''get email of author.'''
1348 r = author.find('>')
1340 r = author.find('>')
1349 if r == -1: r = None
1341 if r == -1: r = None
1350 return author[author.find('<')+1:r]
1342 return author[author.find('<')+1:r]
1351
1343
1352 def ellipsis(text, maxlength=400):
1344 def ellipsis(text, maxlength=400):
1353 """Trim string to at most maxlength (default: 400) characters."""
1345 """Trim string to at most maxlength (default: 400) characters."""
1354 if len(text) <= maxlength:
1346 if len(text) <= maxlength:
1355 return text
1347 return text
1356 else:
1348 else:
1357 return "%s..." % (text[:maxlength-3])
1349 return "%s..." % (text[:maxlength-3])
1358
1350
1359 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
1351 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
1360 '''yield every hg repository under path, recursively.'''
1352 '''yield every hg repository under path, recursively.'''
1361 def errhandler(err):
1353 def errhandler(err):
1362 if err.filename == path:
1354 if err.filename == path:
1363 raise err
1355 raise err
1364 if followsym and hasattr(os.path, 'samestat'):
1356 if followsym and hasattr(os.path, 'samestat'):
1365 def _add_dir_if_not_there(dirlst, dirname):
1357 def _add_dir_if_not_there(dirlst, dirname):
1366 match = False
1358 match = False
1367 samestat = os.path.samestat
1359 samestat = os.path.samestat
1368 dirstat = os.stat(dirname)
1360 dirstat = os.stat(dirname)
1369 for lstdirstat in dirlst:
1361 for lstdirstat in dirlst:
1370 if samestat(dirstat, lstdirstat):
1362 if samestat(dirstat, lstdirstat):
1371 match = True
1363 match = True
1372 break
1364 break
1373 if not match:
1365 if not match:
1374 dirlst.append(dirstat)
1366 dirlst.append(dirstat)
1375 return not match
1367 return not match
1376 else:
1368 else:
1377 followsym = False
1369 followsym = False
1378
1370
1379 if (seen_dirs is None) and followsym:
1371 if (seen_dirs is None) and followsym:
1380 seen_dirs = []
1372 seen_dirs = []
1381 _add_dir_if_not_there(seen_dirs, path)
1373 _add_dir_if_not_there(seen_dirs, path)
1382 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
1374 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
1383 if '.hg' in dirs:
1375 if '.hg' in dirs:
1384 yield root # found a repository
1376 yield root # found a repository
1385 qroot = os.path.join(root, '.hg', 'patches')
1377 qroot = os.path.join(root, '.hg', 'patches')
1386 if os.path.isdir(os.path.join(qroot, '.hg')):
1378 if os.path.isdir(os.path.join(qroot, '.hg')):
1387 yield qroot # we have a patch queue repo here
1379 yield qroot # we have a patch queue repo here
1388 if recurse:
1380 if recurse:
1389 # avoid recursing inside the .hg directory
1381 # avoid recursing inside the .hg directory
1390 dirs.remove('.hg')
1382 dirs.remove('.hg')
1391 else:
1383 else:
1392 dirs[:] = [] # don't descend further
1384 dirs[:] = [] # don't descend further
1393 elif followsym:
1385 elif followsym:
1394 newdirs = []
1386 newdirs = []
1395 for d in dirs:
1387 for d in dirs:
1396 fname = os.path.join(root, d)
1388 fname = os.path.join(root, d)
1397 if _add_dir_if_not_there(seen_dirs, fname):
1389 if _add_dir_if_not_there(seen_dirs, fname):
1398 if os.path.islink(fname):
1390 if os.path.islink(fname):
1399 for hgname in walkrepos(fname, True, seen_dirs):
1391 for hgname in walkrepos(fname, True, seen_dirs):
1400 yield hgname
1392 yield hgname
1401 else:
1393 else:
1402 newdirs.append(d)
1394 newdirs.append(d)
1403 dirs[:] = newdirs
1395 dirs[:] = newdirs
1404
1396
1405 _rcpath = None
1397 _rcpath = None
1406
1398
1407 def os_rcpath():
1399 def os_rcpath():
1408 '''return default os-specific hgrc search path'''
1400 '''return default os-specific hgrc search path'''
1409 path = system_rcpath()
1401 path = system_rcpath()
1410 path.extend(user_rcpath())
1402 path.extend(user_rcpath())
1411 path = [os.path.normpath(f) for f in path]
1403 path = [os.path.normpath(f) for f in path]
1412 return path
1404 return path
1413
1405
1414 def rcpath():
1406 def rcpath():
1415 '''return hgrc search path. if env var HGRCPATH is set, use it.
1407 '''return hgrc search path. if env var HGRCPATH is set, use it.
1416 for each item in path, if directory, use files ending in .rc,
1408 for each item in path, if directory, use files ending in .rc,
1417 else use item.
1409 else use item.
1418 make HGRCPATH empty to only look in .hg/hgrc of current repo.
1410 make HGRCPATH empty to only look in .hg/hgrc of current repo.
1419 if no HGRCPATH, use default os-specific path.'''
1411 if no HGRCPATH, use default os-specific path.'''
1420 global _rcpath
1412 global _rcpath
1421 if _rcpath is None:
1413 if _rcpath is None:
1422 if 'HGRCPATH' in os.environ:
1414 if 'HGRCPATH' in os.environ:
1423 _rcpath = []
1415 _rcpath = []
1424 for p in os.environ['HGRCPATH'].split(os.pathsep):
1416 for p in os.environ['HGRCPATH'].split(os.pathsep):
1425 if not p: continue
1417 if not p: continue
1426 if os.path.isdir(p):
1418 if os.path.isdir(p):
1427 for f, kind in osutil.listdir(p):
1419 for f, kind in osutil.listdir(p):
1428 if f.endswith('.rc'):
1420 if f.endswith('.rc'):
1429 _rcpath.append(os.path.join(p, f))
1421 _rcpath.append(os.path.join(p, f))
1430 else:
1422 else:
1431 _rcpath.append(p)
1423 _rcpath.append(p)
1432 else:
1424 else:
1433 _rcpath = os_rcpath()
1425 _rcpath = os_rcpath()
1434 return _rcpath
1426 return _rcpath
1435
1427
1436 def bytecount(nbytes):
1428 def bytecount(nbytes):
1437 '''return byte count formatted as readable string, with units'''
1429 '''return byte count formatted as readable string, with units'''
1438
1430
1439 units = (
1431 units = (
1440 (100, 1<<30, _('%.0f GB')),
1432 (100, 1<<30, _('%.0f GB')),
1441 (10, 1<<30, _('%.1f GB')),
1433 (10, 1<<30, _('%.1f GB')),
1442 (1, 1<<30, _('%.2f GB')),
1434 (1, 1<<30, _('%.2f GB')),
1443 (100, 1<<20, _('%.0f MB')),
1435 (100, 1<<20, _('%.0f MB')),
1444 (10, 1<<20, _('%.1f MB')),
1436 (10, 1<<20, _('%.1f MB')),
1445 (1, 1<<20, _('%.2f MB')),
1437 (1, 1<<20, _('%.2f MB')),
1446 (100, 1<<10, _('%.0f KB')),
1438 (100, 1<<10, _('%.0f KB')),
1447 (10, 1<<10, _('%.1f KB')),
1439 (10, 1<<10, _('%.1f KB')),
1448 (1, 1<<10, _('%.2f KB')),
1440 (1, 1<<10, _('%.2f KB')),
1449 (1, 1, _('%.0f bytes')),
1441 (1, 1, _('%.0f bytes')),
1450 )
1442 )
1451
1443
1452 for multiplier, divisor, format in units:
1444 for multiplier, divisor, format in units:
1453 if nbytes >= divisor * multiplier:
1445 if nbytes >= divisor * multiplier:
1454 return format % (nbytes / float(divisor))
1446 return format % (nbytes / float(divisor))
1455 return units[-1][2] % nbytes
1447 return units[-1][2] % nbytes
1456
1448
1457 def drop_scheme(scheme, path):
1449 def drop_scheme(scheme, path):
1458 sc = scheme + ':'
1450 sc = scheme + ':'
1459 if path.startswith(sc):
1451 if path.startswith(sc):
1460 path = path[len(sc):]
1452 path = path[len(sc):]
1461 if path.startswith('//'):
1453 if path.startswith('//'):
1462 path = path[2:]
1454 path = path[2:]
1463 return path
1455 return path
1464
1456
1465 def uirepr(s):
1457 def uirepr(s):
1466 # Avoid double backslash in Windows path repr()
1458 # Avoid double backslash in Windows path repr()
1467 return repr(s).replace('\\\\', '\\')
1459 return repr(s).replace('\\\\', '\\')
1468
1460
1469 def termwidth():
1461 def termwidth():
1470 if 'COLUMNS' in os.environ:
1462 if 'COLUMNS' in os.environ:
1471 try:
1463 try:
1472 return int(os.environ['COLUMNS'])
1464 return int(os.environ['COLUMNS'])
1473 except ValueError:
1465 except ValueError:
1474 pass
1466 pass
1475 try:
1467 try:
1476 import termios, array, fcntl
1468 import termios, array, fcntl
1477 for dev in (sys.stdout, sys.stdin):
1469 for dev in (sys.stdout, sys.stdin):
1478 try:
1470 try:
1479 fd = dev.fileno()
1471 fd = dev.fileno()
1480 if not os.isatty(fd):
1472 if not os.isatty(fd):
1481 continue
1473 continue
1482 arri = fcntl.ioctl(fd, termios.TIOCGWINSZ, '\0' * 8)
1474 arri = fcntl.ioctl(fd, termios.TIOCGWINSZ, '\0' * 8)
1483 return array.array('h', arri)[1]
1475 return array.array('h', arri)[1]
1484 except ValueError:
1476 except ValueError:
1485 pass
1477 pass
1486 except ImportError:
1478 except ImportError:
1487 pass
1479 pass
1488 return 80
1480 return 80
1489
1481
1490 def iterlines(iterator):
1482 def iterlines(iterator):
1491 for chunk in iterator:
1483 for chunk in iterator:
1492 for line in chunk.splitlines():
1484 for line in chunk.splitlines():
1493 yield line
1485 yield line
@@ -1,708 +1,704
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 #
2 #
3 # run-tests.py - Run a set of tests on Mercurial
3 # run-tests.py - Run a set of tests on Mercurial
4 #
4 #
5 # Copyright 2006 Matt Mackall <mpm@selenic.com>
5 # Copyright 2006 Matt Mackall <mpm@selenic.com>
6 #
6 #
7 # This software may be used and distributed according to the terms of the
7 # This software may be used and distributed according to the terms of the
8 # GNU General Public License version 2, incorporated herein by reference.
8 # GNU General Public License version 2, incorporated herein by reference.
9
9
10 import difflib
10 import difflib
11 import errno
11 import errno
12 import optparse
12 import optparse
13 import os
13 import os
14 try:
14 import subprocess
15 import subprocess
16 subprocess.Popen # trigger ImportError early
17 closefds = os.name == 'posix'
18 def Popen4(cmd, bufsize=-1):
19 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
20 close_fds=closefds,
21 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
22 stderr=subprocess.STDOUT)
23 p.fromchild = p.stdout
24 p.tochild = p.stdin
25 p.childerr = p.stderr
26 return p
27 except ImportError:
28 subprocess = None
29 from popen2 import Popen4
30 import shutil
15 import shutil
31 import signal
16 import signal
32 import sys
17 import sys
33 import tempfile
18 import tempfile
34 import time
19 import time
35
20
21 closefds = os.name == 'posix'
22 def Popen4(cmd, bufsize=-1):
23 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
24 close_fds=closefds,
25 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
26 stderr=subprocess.STDOUT)
27 p.fromchild = p.stdout
28 p.tochild = p.stdin
29 p.childerr = p.stderr
30 return p
31
36 # reserved exit code to skip test (used by hghave)
32 # reserved exit code to skip test (used by hghave)
37 SKIPPED_STATUS = 80
33 SKIPPED_STATUS = 80
38 SKIPPED_PREFIX = 'skipped: '
34 SKIPPED_PREFIX = 'skipped: '
39 FAILED_PREFIX = 'hghave check failed: '
35 FAILED_PREFIX = 'hghave check failed: '
40 PYTHON = sys.executable
36 PYTHON = sys.executable
41 hgpkg = None
37 hgpkg = None
42
38
43 requiredtools = ["python", "diff", "grep", "unzip", "gunzip", "bunzip2", "sed"]
39 requiredtools = ["python", "diff", "grep", "unzip", "gunzip", "bunzip2", "sed"]
44
40
45 defaults = {
41 defaults = {
46 'jobs': ('HGTEST_JOBS', 1),
42 'jobs': ('HGTEST_JOBS', 1),
47 'timeout': ('HGTEST_TIMEOUT', 180),
43 'timeout': ('HGTEST_TIMEOUT', 180),
48 'port': ('HGTEST_PORT', 20059),
44 'port': ('HGTEST_PORT', 20059),
49 }
45 }
50
46
51 def parseargs():
47 def parseargs():
52 parser = optparse.OptionParser("%prog [options] [tests]")
48 parser = optparse.OptionParser("%prog [options] [tests]")
53 parser.add_option("-C", "--annotate", action="store_true",
49 parser.add_option("-C", "--annotate", action="store_true",
54 help="output files annotated with coverage")
50 help="output files annotated with coverage")
55 parser.add_option("--child", type="int",
51 parser.add_option("--child", type="int",
56 help="run as child process, summary to given fd")
52 help="run as child process, summary to given fd")
57 parser.add_option("-c", "--cover", action="store_true",
53 parser.add_option("-c", "--cover", action="store_true",
58 help="print a test coverage report")
54 help="print a test coverage report")
59 parser.add_option("-f", "--first", action="store_true",
55 parser.add_option("-f", "--first", action="store_true",
60 help="exit on the first test failure")
56 help="exit on the first test failure")
61 parser.add_option("-i", "--interactive", action="store_true",
57 parser.add_option("-i", "--interactive", action="store_true",
62 help="prompt to accept changed output")
58 help="prompt to accept changed output")
63 parser.add_option("-j", "--jobs", type="int",
59 parser.add_option("-j", "--jobs", type="int",
64 help="number of jobs to run in parallel"
60 help="number of jobs to run in parallel"
65 " (default: $%s or %d)" % defaults['jobs'])
61 " (default: $%s or %d)" % defaults['jobs'])
66 parser.add_option("--keep-tmpdir", action="store_true",
62 parser.add_option("--keep-tmpdir", action="store_true",
67 help="keep temporary directory after running tests"
63 help="keep temporary directory after running tests"
68 " (best used with --tmpdir)")
64 " (best used with --tmpdir)")
69 parser.add_option("-R", "--restart", action="store_true",
65 parser.add_option("-R", "--restart", action="store_true",
70 help="restart at last error")
66 help="restart at last error")
71 parser.add_option("-p", "--port", type="int",
67 parser.add_option("-p", "--port", type="int",
72 help="port on which servers should listen"
68 help="port on which servers should listen"
73 " (default: $%s or %d)" % defaults['port'])
69 " (default: $%s or %d)" % defaults['port'])
74 parser.add_option("-r", "--retest", action="store_true",
70 parser.add_option("-r", "--retest", action="store_true",
75 help="retest failed tests")
71 help="retest failed tests")
76 parser.add_option("-s", "--cover_stdlib", action="store_true",
72 parser.add_option("-s", "--cover_stdlib", action="store_true",
77 help="print a test coverage report inc. standard libraries")
73 help="print a test coverage report inc. standard libraries")
78 parser.add_option("-t", "--timeout", type="int",
74 parser.add_option("-t", "--timeout", type="int",
79 help="kill errant tests after TIMEOUT seconds"
75 help="kill errant tests after TIMEOUT seconds"
80 " (default: $%s or %d)" % defaults['timeout'])
76 " (default: $%s or %d)" % defaults['timeout'])
81 parser.add_option("--tmpdir", type="string",
77 parser.add_option("--tmpdir", type="string",
82 help="run tests in the given temporary directory")
78 help="run tests in the given temporary directory")
83 parser.add_option("-v", "--verbose", action="store_true",
79 parser.add_option("-v", "--verbose", action="store_true",
84 help="output verbose messages")
80 help="output verbose messages")
85 parser.add_option("-n", "--nodiff", action="store_true",
81 parser.add_option("-n", "--nodiff", action="store_true",
86 help="skip showing test changes")
82 help="skip showing test changes")
87 parser.add_option("--with-hg", type="string",
83 parser.add_option("--with-hg", type="string",
88 help="test existing install at given location")
84 help="test existing install at given location")
89 parser.add_option("--pure", action="store_true",
85 parser.add_option("--pure", action="store_true",
90 help="use pure Python code instead of C extensions")
86 help="use pure Python code instead of C extensions")
91
87
92 for option, default in defaults.items():
88 for option, default in defaults.items():
93 defaults[option] = int(os.environ.get(*default))
89 defaults[option] = int(os.environ.get(*default))
94 parser.set_defaults(**defaults)
90 parser.set_defaults(**defaults)
95 (options, args) = parser.parse_args()
91 (options, args) = parser.parse_args()
96
92
97 global vlog
93 global vlog
98 options.anycoverage = (options.cover or
94 options.anycoverage = (options.cover or
99 options.cover_stdlib or
95 options.cover_stdlib or
100 options.annotate)
96 options.annotate)
101
97
102 if options.verbose:
98 if options.verbose:
103 def vlog(*msg):
99 def vlog(*msg):
104 for m in msg:
100 for m in msg:
105 print m,
101 print m,
106 print
102 print
107 else:
103 else:
108 vlog = lambda *msg: None
104 vlog = lambda *msg: None
109
105
110 if options.jobs < 1:
106 if options.jobs < 1:
111 print >> sys.stderr, 'ERROR: -j/--jobs must be positive'
107 print >> sys.stderr, 'ERROR: -j/--jobs must be positive'
112 sys.exit(1)
108 sys.exit(1)
113 if options.interactive and options.jobs > 1:
109 if options.interactive and options.jobs > 1:
114 print '(--interactive overrides --jobs)'
110 print '(--interactive overrides --jobs)'
115 options.jobs = 1
111 options.jobs = 1
116
112
117 return (options, args)
113 return (options, args)
118
114
119 def rename(src, dst):
115 def rename(src, dst):
120 """Like os.rename(), trade atomicity and opened files friendliness
116 """Like os.rename(), trade atomicity and opened files friendliness
121 for existing destination support.
117 for existing destination support.
122 """
118 """
123 shutil.copy(src, dst)
119 shutil.copy(src, dst)
124 os.remove(src)
120 os.remove(src)
125
121
126 def splitnewlines(text):
122 def splitnewlines(text):
127 '''like str.splitlines, but only split on newlines.
123 '''like str.splitlines, but only split on newlines.
128 keep line endings.'''
124 keep line endings.'''
129 i = 0
125 i = 0
130 lines = []
126 lines = []
131 while True:
127 while True:
132 n = text.find('\n', i)
128 n = text.find('\n', i)
133 if n == -1:
129 if n == -1:
134 last = text[i:]
130 last = text[i:]
135 if last:
131 if last:
136 lines.append(last)
132 lines.append(last)
137 return lines
133 return lines
138 lines.append(text[i:n+1])
134 lines.append(text[i:n+1])
139 i = n + 1
135 i = n + 1
140
136
141 def parsehghaveoutput(lines):
137 def parsehghaveoutput(lines):
142 '''Parse hghave log lines.
138 '''Parse hghave log lines.
143 Return tuple of lists (missing, failed):
139 Return tuple of lists (missing, failed):
144 * the missing/unknown features
140 * the missing/unknown features
145 * the features for which existence check failed'''
141 * the features for which existence check failed'''
146 missing = []
142 missing = []
147 failed = []
143 failed = []
148 for line in lines:
144 for line in lines:
149 if line.startswith(SKIPPED_PREFIX):
145 if line.startswith(SKIPPED_PREFIX):
150 line = line.splitlines()[0]
146 line = line.splitlines()[0]
151 missing.append(line[len(SKIPPED_PREFIX):])
147 missing.append(line[len(SKIPPED_PREFIX):])
152 elif line.startswith(FAILED_PREFIX):
148 elif line.startswith(FAILED_PREFIX):
153 line = line.splitlines()[0]
149 line = line.splitlines()[0]
154 failed.append(line[len(FAILED_PREFIX):])
150 failed.append(line[len(FAILED_PREFIX):])
155
151
156 return missing, failed
152 return missing, failed
157
153
158 def showdiff(expected, output):
154 def showdiff(expected, output):
159 for line in difflib.unified_diff(expected, output,
155 for line in difflib.unified_diff(expected, output,
160 "Expected output", "Test output"):
156 "Expected output", "Test output"):
161 sys.stdout.write(line)
157 sys.stdout.write(line)
162
158
163 def findprogram(program):
159 def findprogram(program):
164 """Search PATH for a executable program"""
160 """Search PATH for a executable program"""
165 for p in os.environ.get('PATH', os.defpath).split(os.pathsep):
161 for p in os.environ.get('PATH', os.defpath).split(os.pathsep):
166 name = os.path.join(p, program)
162 name = os.path.join(p, program)
167 if os.access(name, os.X_OK):
163 if os.access(name, os.X_OK):
168 return name
164 return name
169 return None
165 return None
170
166
171 def checktools():
167 def checktools():
172 # Before we go any further, check for pre-requisite tools
168 # Before we go any further, check for pre-requisite tools
173 # stuff from coreutils (cat, rm, etc) are not tested
169 # stuff from coreutils (cat, rm, etc) are not tested
174 for p in requiredtools:
170 for p in requiredtools:
175 if os.name == 'nt':
171 if os.name == 'nt':
176 p += '.exe'
172 p += '.exe'
177 found = findprogram(p)
173 found = findprogram(p)
178 if found:
174 if found:
179 vlog("# Found prerequisite", p, "at", found)
175 vlog("# Found prerequisite", p, "at", found)
180 else:
176 else:
181 print "WARNING: Did not find prerequisite tool: "+p
177 print "WARNING: Did not find prerequisite tool: "+p
182
178
183 def cleanup(options):
179 def cleanup(options):
184 if not options.keep_tmpdir:
180 if not options.keep_tmpdir:
185 if options.verbose:
181 if options.verbose:
186 print "# Cleaning up HGTMP", HGTMP
182 print "# Cleaning up HGTMP", HGTMP
187 shutil.rmtree(HGTMP, True)
183 shutil.rmtree(HGTMP, True)
188
184
189 def usecorrectpython():
185 def usecorrectpython():
190 # some tests run python interpreter. they must use same
186 # some tests run python interpreter. they must use same
191 # interpreter we use or bad things will happen.
187 # interpreter we use or bad things will happen.
192 exedir, exename = os.path.split(sys.executable)
188 exedir, exename = os.path.split(sys.executable)
193 if exename == 'python':
189 if exename == 'python':
194 path = findprogram('python')
190 path = findprogram('python')
195 if os.path.dirname(path) == exedir:
191 if os.path.dirname(path) == exedir:
196 return
192 return
197 vlog('# Making python executable in test path use correct Python')
193 vlog('# Making python executable in test path use correct Python')
198 mypython = os.path.join(BINDIR, 'python')
194 mypython = os.path.join(BINDIR, 'python')
199 try:
195 try:
200 os.symlink(sys.executable, mypython)
196 os.symlink(sys.executable, mypython)
201 except AttributeError:
197 except AttributeError:
202 # windows fallback
198 # windows fallback
203 shutil.copyfile(sys.executable, mypython)
199 shutil.copyfile(sys.executable, mypython)
204 shutil.copymode(sys.executable, mypython)
200 shutil.copymode(sys.executable, mypython)
205
201
206 def installhg(options):
202 def installhg(options):
207 global PYTHON
203 global PYTHON
208 vlog("# Performing temporary installation of HG")
204 vlog("# Performing temporary installation of HG")
209 installerrs = os.path.join("tests", "install.err")
205 installerrs = os.path.join("tests", "install.err")
210 pure = options.pure and "--pure" or ""
206 pure = options.pure and "--pure" or ""
211
207
212 # Run installer in hg root
208 # Run installer in hg root
213 os.chdir(os.path.join(os.path.dirname(sys.argv[0]), '..'))
209 os.chdir(os.path.join(os.path.dirname(sys.argv[0]), '..'))
214 cmd = ('%s setup.py %s clean --all'
210 cmd = ('%s setup.py %s clean --all'
215 ' install --force --prefix="%s" --install-lib="%s"'
211 ' install --force --prefix="%s" --install-lib="%s"'
216 ' --install-scripts="%s" >%s 2>&1'
212 ' --install-scripts="%s" >%s 2>&1'
217 % (sys.executable, pure, INST, PYTHONDIR, BINDIR, installerrs))
213 % (sys.executable, pure, INST, PYTHONDIR, BINDIR, installerrs))
218 vlog("# Running", cmd)
214 vlog("# Running", cmd)
219 if os.system(cmd) == 0:
215 if os.system(cmd) == 0:
220 if not options.verbose:
216 if not options.verbose:
221 os.remove(installerrs)
217 os.remove(installerrs)
222 else:
218 else:
223 f = open(installerrs)
219 f = open(installerrs)
224 for line in f:
220 for line in f:
225 print line,
221 print line,
226 f.close()
222 f.close()
227 sys.exit(1)
223 sys.exit(1)
228 os.chdir(TESTDIR)
224 os.chdir(TESTDIR)
229
225
230 os.environ["PATH"] = "%s%s%s" % (BINDIR, os.pathsep, os.environ["PATH"])
226 os.environ["PATH"] = "%s%s%s" % (BINDIR, os.pathsep, os.environ["PATH"])
231
227
232 pydir = os.pathsep.join([PYTHONDIR, TESTDIR])
228 pydir = os.pathsep.join([PYTHONDIR, TESTDIR])
233 pythonpath = os.environ.get("PYTHONPATH")
229 pythonpath = os.environ.get("PYTHONPATH")
234 if pythonpath:
230 if pythonpath:
235 pythonpath = pydir + os.pathsep + pythonpath
231 pythonpath = pydir + os.pathsep + pythonpath
236 else:
232 else:
237 pythonpath = pydir
233 pythonpath = pydir
238 os.environ["PYTHONPATH"] = pythonpath
234 os.environ["PYTHONPATH"] = pythonpath
239
235
240 usecorrectpython()
236 usecorrectpython()
241 global hgpkg
237 global hgpkg
242 hgpkg = _hgpath()
238 hgpkg = _hgpath()
243
239
244 vlog("# Installing dummy diffstat")
240 vlog("# Installing dummy diffstat")
245 f = open(os.path.join(BINDIR, 'diffstat'), 'w')
241 f = open(os.path.join(BINDIR, 'diffstat'), 'w')
246 f.write('#!' + sys.executable + '\n'
242 f.write('#!' + sys.executable + '\n'
247 'import sys\n'
243 'import sys\n'
248 'files = 0\n'
244 'files = 0\n'
249 'for line in sys.stdin:\n'
245 'for line in sys.stdin:\n'
250 ' if line.startswith("diff "):\n'
246 ' if line.startswith("diff "):\n'
251 ' files += 1\n'
247 ' files += 1\n'
252 'sys.stdout.write("files patched: %d\\n" % files)\n')
248 'sys.stdout.write("files patched: %d\\n" % files)\n')
253 f.close()
249 f.close()
254 os.chmod(os.path.join(BINDIR, 'diffstat'), 0700)
250 os.chmod(os.path.join(BINDIR, 'diffstat'), 0700)
255
251
256 if options.anycoverage:
252 if options.anycoverage:
257 vlog("# Installing coverage wrapper")
253 vlog("# Installing coverage wrapper")
258 os.environ['COVERAGE_FILE'] = COVERAGE_FILE
254 os.environ['COVERAGE_FILE'] = COVERAGE_FILE
259 if os.path.exists(COVERAGE_FILE):
255 if os.path.exists(COVERAGE_FILE):
260 os.unlink(COVERAGE_FILE)
256 os.unlink(COVERAGE_FILE)
261 # Create a wrapper script to invoke hg via coverage.py
257 # Create a wrapper script to invoke hg via coverage.py
262 os.rename(os.path.join(BINDIR, "hg"), os.path.join(BINDIR, "_hg.py"))
258 os.rename(os.path.join(BINDIR, "hg"), os.path.join(BINDIR, "_hg.py"))
263 f = open(os.path.join(BINDIR, 'hg'), 'w')
259 f = open(os.path.join(BINDIR, 'hg'), 'w')
264 f.write('#!' + sys.executable + '\n')
260 f.write('#!' + sys.executable + '\n')
265 f.write('import sys, os; os.execv(sys.executable, [sys.executable, '
261 f.write('import sys, os; os.execv(sys.executable, [sys.executable, '
266 '"%s", "-x", "%s"] + sys.argv[1:])\n' %
262 '"%s", "-x", "%s"] + sys.argv[1:])\n' %
267 (os.path.join(TESTDIR, 'coverage.py'),
263 (os.path.join(TESTDIR, 'coverage.py'),
268 os.path.join(BINDIR, '_hg.py')))
264 os.path.join(BINDIR, '_hg.py')))
269 f.close()
265 f.close()
270 os.chmod(os.path.join(BINDIR, 'hg'), 0700)
266 os.chmod(os.path.join(BINDIR, 'hg'), 0700)
271 PYTHON = '"%s" "%s" -x' % (sys.executable,
267 PYTHON = '"%s" "%s" -x' % (sys.executable,
272 os.path.join(TESTDIR,'coverage.py'))
268 os.path.join(TESTDIR,'coverage.py'))
273
269
274 def _hgpath():
270 def _hgpath():
275 cmd = '%s -c "import mercurial; print mercurial.__path__[0]"'
271 cmd = '%s -c "import mercurial; print mercurial.__path__[0]"'
276 hgpath = os.popen(cmd % PYTHON)
272 hgpath = os.popen(cmd % PYTHON)
277 path = hgpath.read().strip()
273 path = hgpath.read().strip()
278 hgpath.close()
274 hgpath.close()
279 return path
275 return path
280
276
281 def outputcoverage(options):
277 def outputcoverage(options):
282 vlog("# Producing coverage report")
278 vlog("# Producing coverage report")
283 omit = [BINDIR, TESTDIR, PYTHONDIR]
279 omit = [BINDIR, TESTDIR, PYTHONDIR]
284 if not options.cover_stdlib:
280 if not options.cover_stdlib:
285 # Exclude as system paths (ignoring empty strings seen on win)
281 # Exclude as system paths (ignoring empty strings seen on win)
286 omit += [x for x in sys.path if x != '']
282 omit += [x for x in sys.path if x != '']
287 omit = ','.join(omit)
283 omit = ','.join(omit)
288 os.chdir(PYTHONDIR)
284 os.chdir(PYTHONDIR)
289 cmd = '"%s" "%s" -i -r "--omit=%s"' % (
285 cmd = '"%s" "%s" -i -r "--omit=%s"' % (
290 sys.executable, os.path.join(TESTDIR, 'coverage.py'), omit)
286 sys.executable, os.path.join(TESTDIR, 'coverage.py'), omit)
291 vlog("# Running: "+cmd)
287 vlog("# Running: "+cmd)
292 os.system(cmd)
288 os.system(cmd)
293 if options.annotate:
289 if options.annotate:
294 adir = os.path.join(TESTDIR, 'annotated')
290 adir = os.path.join(TESTDIR, 'annotated')
295 if not os.path.isdir(adir):
291 if not os.path.isdir(adir):
296 os.mkdir(adir)
292 os.mkdir(adir)
297 cmd = '"%s" "%s" -i -a "--directory=%s" "--omit=%s"' % (
293 cmd = '"%s" "%s" -i -a "--directory=%s" "--omit=%s"' % (
298 sys.executable, os.path.join(TESTDIR, 'coverage.py'),
294 sys.executable, os.path.join(TESTDIR, 'coverage.py'),
299 adir, omit)
295 adir, omit)
300 vlog("# Running: "+cmd)
296 vlog("# Running: "+cmd)
301 os.system(cmd)
297 os.system(cmd)
302
298
303 class Timeout(Exception):
299 class Timeout(Exception):
304 pass
300 pass
305
301
306 def alarmed(signum, frame):
302 def alarmed(signum, frame):
307 raise Timeout
303 raise Timeout
308
304
309 def run(cmd, options):
305 def run(cmd, options):
310 """Run command in a sub-process, capturing the output (stdout and stderr).
306 """Run command in a sub-process, capturing the output (stdout and stderr).
311 Return the exist code, and output."""
307 Return the exist code, and output."""
312 # TODO: Use subprocess.Popen if we're running on Python 2.4
308 # TODO: Use subprocess.Popen if we're running on Python 2.4
313 if os.name == 'nt' or sys.platform.startswith('java'):
309 if os.name == 'nt' or sys.platform.startswith('java'):
314 tochild, fromchild = os.popen4(cmd)
310 tochild, fromchild = os.popen4(cmd)
315 tochild.close()
311 tochild.close()
316 output = fromchild.read()
312 output = fromchild.read()
317 ret = fromchild.close()
313 ret = fromchild.close()
318 if ret == None:
314 if ret == None:
319 ret = 0
315 ret = 0
320 else:
316 else:
321 proc = Popen4(cmd)
317 proc = Popen4(cmd)
322 try:
318 try:
323 output = ''
319 output = ''
324 proc.tochild.close()
320 proc.tochild.close()
325 output = proc.fromchild.read()
321 output = proc.fromchild.read()
326 ret = proc.wait()
322 ret = proc.wait()
327 if os.WIFEXITED(ret):
323 if os.WIFEXITED(ret):
328 ret = os.WEXITSTATUS(ret)
324 ret = os.WEXITSTATUS(ret)
329 except Timeout:
325 except Timeout:
330 vlog('# Process %d timed out - killing it' % proc.pid)
326 vlog('# Process %d timed out - killing it' % proc.pid)
331 os.kill(proc.pid, signal.SIGTERM)
327 os.kill(proc.pid, signal.SIGTERM)
332 ret = proc.wait()
328 ret = proc.wait()
333 if ret == 0:
329 if ret == 0:
334 ret = signal.SIGTERM << 8
330 ret = signal.SIGTERM << 8
335 output += ("\n### Abort: timeout after %d seconds.\n"
331 output += ("\n### Abort: timeout after %d seconds.\n"
336 % options.timeout)
332 % options.timeout)
337 return ret, splitnewlines(output)
333 return ret, splitnewlines(output)
338
334
339 def runone(options, test, skips, fails):
335 def runone(options, test, skips, fails):
340 '''tristate output:
336 '''tristate output:
341 None -> skipped
337 None -> skipped
342 True -> passed
338 True -> passed
343 False -> failed'''
339 False -> failed'''
344
340
345 def skip(msg):
341 def skip(msg):
346 if not options.verbose:
342 if not options.verbose:
347 skips.append((test, msg))
343 skips.append((test, msg))
348 else:
344 else:
349 print "\nSkipping %s: %s" % (test, msg)
345 print "\nSkipping %s: %s" % (test, msg)
350 return None
346 return None
351
347
352 def fail(msg):
348 def fail(msg):
353 fails.append((test, msg))
349 fails.append((test, msg))
354 if not options.nodiff:
350 if not options.nodiff:
355 print "\nERROR: %s %s" % (test, msg)
351 print "\nERROR: %s %s" % (test, msg)
356 return None
352 return None
357
353
358 vlog("# Test", test)
354 vlog("# Test", test)
359
355
360 # create a fresh hgrc
356 # create a fresh hgrc
361 hgrc = file(HGRCPATH, 'w+')
357 hgrc = file(HGRCPATH, 'w+')
362 hgrc.write('[ui]\n')
358 hgrc.write('[ui]\n')
363 hgrc.write('slash = True\n')
359 hgrc.write('slash = True\n')
364 hgrc.write('[defaults]\n')
360 hgrc.write('[defaults]\n')
365 hgrc.write('backout = -d "0 0"\n')
361 hgrc.write('backout = -d "0 0"\n')
366 hgrc.write('commit = -d "0 0"\n')
362 hgrc.write('commit = -d "0 0"\n')
367 hgrc.write('debugrawcommit = -d "0 0"\n')
363 hgrc.write('debugrawcommit = -d "0 0"\n')
368 hgrc.write('tag = -d "0 0"\n')
364 hgrc.write('tag = -d "0 0"\n')
369 hgrc.close()
365 hgrc.close()
370
366
371 err = os.path.join(TESTDIR, test+".err")
367 err = os.path.join(TESTDIR, test+".err")
372 ref = os.path.join(TESTDIR, test+".out")
368 ref = os.path.join(TESTDIR, test+".out")
373 testpath = os.path.join(TESTDIR, test)
369 testpath = os.path.join(TESTDIR, test)
374
370
375 if os.path.exists(err):
371 if os.path.exists(err):
376 os.remove(err) # Remove any previous output files
372 os.remove(err) # Remove any previous output files
377
373
378 # Make a tmp subdirectory to work in
374 # Make a tmp subdirectory to work in
379 tmpd = os.path.join(HGTMP, test)
375 tmpd = os.path.join(HGTMP, test)
380 os.mkdir(tmpd)
376 os.mkdir(tmpd)
381 os.chdir(tmpd)
377 os.chdir(tmpd)
382
378
383 try:
379 try:
384 tf = open(testpath)
380 tf = open(testpath)
385 firstline = tf.readline().rstrip()
381 firstline = tf.readline().rstrip()
386 tf.close()
382 tf.close()
387 except:
383 except:
388 firstline = ''
384 firstline = ''
389 lctest = test.lower()
385 lctest = test.lower()
390
386
391 if lctest.endswith('.py') or firstline == '#!/usr/bin/env python':
387 if lctest.endswith('.py') or firstline == '#!/usr/bin/env python':
392 cmd = '%s "%s"' % (PYTHON, testpath)
388 cmd = '%s "%s"' % (PYTHON, testpath)
393 elif lctest.endswith('.bat'):
389 elif lctest.endswith('.bat'):
394 # do not run batch scripts on non-windows
390 # do not run batch scripts on non-windows
395 if os.name != 'nt':
391 if os.name != 'nt':
396 return skip("batch script")
392 return skip("batch script")
397 # To reliably get the error code from batch files on WinXP,
393 # To reliably get the error code from batch files on WinXP,
398 # the "cmd /c call" prefix is needed. Grrr
394 # the "cmd /c call" prefix is needed. Grrr
399 cmd = 'cmd /c call "%s"' % testpath
395 cmd = 'cmd /c call "%s"' % testpath
400 else:
396 else:
401 # do not run shell scripts on windows
397 # do not run shell scripts on windows
402 if os.name == 'nt':
398 if os.name == 'nt':
403 return skip("shell script")
399 return skip("shell script")
404 # do not try to run non-executable programs
400 # do not try to run non-executable programs
405 if not os.path.exists(testpath):
401 if not os.path.exists(testpath):
406 return fail("does not exist")
402 return fail("does not exist")
407 elif not os.access(testpath, os.X_OK):
403 elif not os.access(testpath, os.X_OK):
408 return skip("not executable")
404 return skip("not executable")
409 cmd = '"%s"' % testpath
405 cmd = '"%s"' % testpath
410
406
411 if options.timeout > 0:
407 if options.timeout > 0:
412 signal.alarm(options.timeout)
408 signal.alarm(options.timeout)
413
409
414 vlog("# Running", cmd)
410 vlog("# Running", cmd)
415 ret, out = run(cmd, options)
411 ret, out = run(cmd, options)
416 vlog("# Ret was:", ret)
412 vlog("# Ret was:", ret)
417
413
418 if options.timeout > 0:
414 if options.timeout > 0:
419 signal.alarm(0)
415 signal.alarm(0)
420
416
421 mark = '.'
417 mark = '.'
422
418
423 skipped = (ret == SKIPPED_STATUS)
419 skipped = (ret == SKIPPED_STATUS)
424 # If reference output file exists, check test output against it
420 # If reference output file exists, check test output against it
425 if os.path.exists(ref):
421 if os.path.exists(ref):
426 f = open(ref, "r")
422 f = open(ref, "r")
427 refout = splitnewlines(f.read())
423 refout = splitnewlines(f.read())
428 f.close()
424 f.close()
429 else:
425 else:
430 refout = []
426 refout = []
431 if skipped:
427 if skipped:
432 mark = 's'
428 mark = 's'
433 missing, failed = parsehghaveoutput(out)
429 missing, failed = parsehghaveoutput(out)
434 if not missing:
430 if not missing:
435 missing = ['irrelevant']
431 missing = ['irrelevant']
436 if failed:
432 if failed:
437 fail("hghave failed checking for %s" % failed[-1])
433 fail("hghave failed checking for %s" % failed[-1])
438 skipped = False
434 skipped = False
439 else:
435 else:
440 skip(missing[-1])
436 skip(missing[-1])
441 elif out != refout:
437 elif out != refout:
442 mark = '!'
438 mark = '!'
443 if ret:
439 if ret:
444 fail("output changed and returned error code %d" % ret)
440 fail("output changed and returned error code %d" % ret)
445 else:
441 else:
446 fail("output changed")
442 fail("output changed")
447 if not options.nodiff:
443 if not options.nodiff:
448 showdiff(refout, out)
444 showdiff(refout, out)
449 ret = 1
445 ret = 1
450 elif ret:
446 elif ret:
451 mark = '!'
447 mark = '!'
452 fail("returned error code %d" % ret)
448 fail("returned error code %d" % ret)
453
449
454 if not options.verbose:
450 if not options.verbose:
455 sys.stdout.write(mark)
451 sys.stdout.write(mark)
456 sys.stdout.flush()
452 sys.stdout.flush()
457
453
458 if ret != 0 and not skipped:
454 if ret != 0 and not skipped:
459 # Save errors to a file for diagnosis
455 # Save errors to a file for diagnosis
460 f = open(err, "wb")
456 f = open(err, "wb")
461 for line in out:
457 for line in out:
462 f.write(line)
458 f.write(line)
463 f.close()
459 f.close()
464
460
465 # Kill off any leftover daemon processes
461 # Kill off any leftover daemon processes
466 try:
462 try:
467 fp = file(DAEMON_PIDS)
463 fp = file(DAEMON_PIDS)
468 for line in fp:
464 for line in fp:
469 try:
465 try:
470 pid = int(line)
466 pid = int(line)
471 except ValueError:
467 except ValueError:
472 continue
468 continue
473 try:
469 try:
474 os.kill(pid, 0)
470 os.kill(pid, 0)
475 vlog('# Killing daemon process %d' % pid)
471 vlog('# Killing daemon process %d' % pid)
476 os.kill(pid, signal.SIGTERM)
472 os.kill(pid, signal.SIGTERM)
477 time.sleep(0.25)
473 time.sleep(0.25)
478 os.kill(pid, 0)
474 os.kill(pid, 0)
479 vlog('# Daemon process %d is stuck - really killing it' % pid)
475 vlog('# Daemon process %d is stuck - really killing it' % pid)
480 os.kill(pid, signal.SIGKILL)
476 os.kill(pid, signal.SIGKILL)
481 except OSError, err:
477 except OSError, err:
482 if err.errno != errno.ESRCH:
478 if err.errno != errno.ESRCH:
483 raise
479 raise
484 fp.close()
480 fp.close()
485 os.unlink(DAEMON_PIDS)
481 os.unlink(DAEMON_PIDS)
486 except IOError:
482 except IOError:
487 pass
483 pass
488
484
489 os.chdir(TESTDIR)
485 os.chdir(TESTDIR)
490 if not options.keep_tmpdir:
486 if not options.keep_tmpdir:
491 shutil.rmtree(tmpd, True)
487 shutil.rmtree(tmpd, True)
492 if skipped:
488 if skipped:
493 return None
489 return None
494 return ret == 0
490 return ret == 0
495
491
496 def runchildren(options, expecthg, tests):
492 def runchildren(options, expecthg, tests):
497 if not options.with_hg:
493 if not options.with_hg:
498 installhg(options)
494 installhg(options)
499 if hgpkg != expecthg:
495 if hgpkg != expecthg:
500 print '# Testing unexpected mercurial: %s' % hgpkg
496 print '# Testing unexpected mercurial: %s' % hgpkg
501
497
502 optcopy = dict(options.__dict__)
498 optcopy = dict(options.__dict__)
503 optcopy['jobs'] = 1
499 optcopy['jobs'] = 1
504 optcopy['with_hg'] = INST
500 optcopy['with_hg'] = INST
505 opts = []
501 opts = []
506 for opt, value in optcopy.iteritems():
502 for opt, value in optcopy.iteritems():
507 name = '--' + opt.replace('_', '-')
503 name = '--' + opt.replace('_', '-')
508 if value is True:
504 if value is True:
509 opts.append(name)
505 opts.append(name)
510 elif value is not None:
506 elif value is not None:
511 opts.append(name + '=' + str(value))
507 opts.append(name + '=' + str(value))
512
508
513 tests.reverse()
509 tests.reverse()
514 jobs = [[] for j in xrange(options.jobs)]
510 jobs = [[] for j in xrange(options.jobs)]
515 while tests:
511 while tests:
516 for job in jobs:
512 for job in jobs:
517 if not tests: break
513 if not tests: break
518 job.append(tests.pop())
514 job.append(tests.pop())
519 fps = {}
515 fps = {}
520 for j, job in enumerate(jobs):
516 for j, job in enumerate(jobs):
521 if not job:
517 if not job:
522 continue
518 continue
523 rfd, wfd = os.pipe()
519 rfd, wfd = os.pipe()
524 childopts = ['--child=%d' % wfd, '--port=%d' % (options.port + j * 3)]
520 childopts = ['--child=%d' % wfd, '--port=%d' % (options.port + j * 3)]
525 cmdline = [PYTHON, sys.argv[0]] + opts + childopts + job
521 cmdline = [PYTHON, sys.argv[0]] + opts + childopts + job
526 vlog(' '.join(cmdline))
522 vlog(' '.join(cmdline))
527 fps[os.spawnvp(os.P_NOWAIT, cmdline[0], cmdline)] = os.fdopen(rfd, 'r')
523 fps[os.spawnvp(os.P_NOWAIT, cmdline[0], cmdline)] = os.fdopen(rfd, 'r')
528 os.close(wfd)
524 os.close(wfd)
529 failures = 0
525 failures = 0
530 tested, skipped, failed = 0, 0, 0
526 tested, skipped, failed = 0, 0, 0
531 skips = []
527 skips = []
532 fails = []
528 fails = []
533 while fps:
529 while fps:
534 pid, status = os.wait()
530 pid, status = os.wait()
535 fp = fps.pop(pid)
531 fp = fps.pop(pid)
536 l = fp.read().splitlines()
532 l = fp.read().splitlines()
537 test, skip, fail = map(int, l[:3])
533 test, skip, fail = map(int, l[:3])
538 split = -fail or len(l)
534 split = -fail or len(l)
539 for s in l[3:split]:
535 for s in l[3:split]:
540 skips.append(s.split(" ", 1))
536 skips.append(s.split(" ", 1))
541 for s in l[split:]:
537 for s in l[split:]:
542 fails.append(s.split(" ", 1))
538 fails.append(s.split(" ", 1))
543 tested += test
539 tested += test
544 skipped += skip
540 skipped += skip
545 failed += fail
541 failed += fail
546 vlog('pid %d exited, status %d' % (pid, status))
542 vlog('pid %d exited, status %d' % (pid, status))
547 failures |= status
543 failures |= status
548 print
544 print
549 for s in skips:
545 for s in skips:
550 print "Skipped %s: %s" % (s[0], s[1])
546 print "Skipped %s: %s" % (s[0], s[1])
551 for s in fails:
547 for s in fails:
552 print "Failed %s: %s" % (s[0], s[1])
548 print "Failed %s: %s" % (s[0], s[1])
553
549
554 if hgpkg != expecthg:
550 if hgpkg != expecthg:
555 print '# Tested unexpected mercurial: %s' % hgpkg
551 print '# Tested unexpected mercurial: %s' % hgpkg
556 print "# Ran %d tests, %d skipped, %d failed." % (
552 print "# Ran %d tests, %d skipped, %d failed." % (
557 tested, skipped, failed)
553 tested, skipped, failed)
558 sys.exit(failures != 0)
554 sys.exit(failures != 0)
559
555
560 def runtests(options, expecthg, tests):
556 def runtests(options, expecthg, tests):
561 global DAEMON_PIDS, HGRCPATH
557 global DAEMON_PIDS, HGRCPATH
562 DAEMON_PIDS = os.environ["DAEMON_PIDS"] = os.path.join(HGTMP, 'daemon.pids')
558 DAEMON_PIDS = os.environ["DAEMON_PIDS"] = os.path.join(HGTMP, 'daemon.pids')
563 HGRCPATH = os.environ["HGRCPATH"] = os.path.join(HGTMP, '.hgrc')
559 HGRCPATH = os.environ["HGRCPATH"] = os.path.join(HGTMP, '.hgrc')
564
560
565 try:
561 try:
566 if not options.with_hg:
562 if not options.with_hg:
567 installhg(options)
563 installhg(options)
568
564
569 if hgpkg != expecthg:
565 if hgpkg != expecthg:
570 print '# Testing unexpected mercurial: %s' % hgpkg
566 print '# Testing unexpected mercurial: %s' % hgpkg
571
567
572 if options.timeout > 0:
568 if options.timeout > 0:
573 try:
569 try:
574 signal.signal(signal.SIGALRM, alarmed)
570 signal.signal(signal.SIGALRM, alarmed)
575 vlog('# Running tests with %d-second timeout' %
571 vlog('# Running tests with %d-second timeout' %
576 options.timeout)
572 options.timeout)
577 except AttributeError:
573 except AttributeError:
578 print 'WARNING: cannot run tests with timeouts'
574 print 'WARNING: cannot run tests with timeouts'
579 options.timeout = 0
575 options.timeout = 0
580
576
581 tested = 0
577 tested = 0
582 failed = 0
578 failed = 0
583 skipped = 0
579 skipped = 0
584
580
585 if options.restart:
581 if options.restart:
586 orig = list(tests)
582 orig = list(tests)
587 while tests:
583 while tests:
588 if os.path.exists(tests[0] + ".err"):
584 if os.path.exists(tests[0] + ".err"):
589 break
585 break
590 tests.pop(0)
586 tests.pop(0)
591 if not tests:
587 if not tests:
592 print "running all tests"
588 print "running all tests"
593 tests = orig
589 tests = orig
594
590
595 skips = []
591 skips = []
596 fails = []
592 fails = []
597 for test in tests:
593 for test in tests:
598 if options.retest and not os.path.exists(test + ".err"):
594 if options.retest and not os.path.exists(test + ".err"):
599 skipped += 1
595 skipped += 1
600 continue
596 continue
601 ret = runone(options, test, skips, fails)
597 ret = runone(options, test, skips, fails)
602 if ret is None:
598 if ret is None:
603 skipped += 1
599 skipped += 1
604 elif not ret:
600 elif not ret:
605 if options.interactive:
601 if options.interactive:
606 print "Accept this change? [n] ",
602 print "Accept this change? [n] ",
607 answer = sys.stdin.readline().strip()
603 answer = sys.stdin.readline().strip()
608 if answer.lower() in "y yes".split():
604 if answer.lower() in "y yes".split():
609 rename(test + ".err", test + ".out")
605 rename(test + ".err", test + ".out")
610 tested += 1
606 tested += 1
611 fails.pop()
607 fails.pop()
612 continue
608 continue
613 failed += 1
609 failed += 1
614 if options.first:
610 if options.first:
615 break
611 break
616 tested += 1
612 tested += 1
617
613
618 if options.child:
614 if options.child:
619 fp = os.fdopen(options.child, 'w')
615 fp = os.fdopen(options.child, 'w')
620 fp.write('%d\n%d\n%d\n' % (tested, skipped, failed))
616 fp.write('%d\n%d\n%d\n' % (tested, skipped, failed))
621 for s in skips:
617 for s in skips:
622 fp.write("%s %s\n" % s)
618 fp.write("%s %s\n" % s)
623 for s in fails:
619 for s in fails:
624 fp.write("%s %s\n" % s)
620 fp.write("%s %s\n" % s)
625 fp.close()
621 fp.close()
626 else:
622 else:
627 print
623 print
628 for s in skips:
624 for s in skips:
629 print "Skipped %s: %s" % s
625 print "Skipped %s: %s" % s
630 for s in fails:
626 for s in fails:
631 print "Failed %s: %s" % s
627 print "Failed %s: %s" % s
632 if hgpkg != expecthg:
628 if hgpkg != expecthg:
633 print '# Tested unexpected mercurial: %s' % hgpkg
629 print '# Tested unexpected mercurial: %s' % hgpkg
634 print "# Ran %d tests, %d skipped, %d failed." % (
630 print "# Ran %d tests, %d skipped, %d failed." % (
635 tested, skipped, failed)
631 tested, skipped, failed)
636
632
637 if options.anycoverage:
633 if options.anycoverage:
638 outputcoverage(options)
634 outputcoverage(options)
639 except KeyboardInterrupt:
635 except KeyboardInterrupt:
640 failed = True
636 failed = True
641 print "\ninterrupted!"
637 print "\ninterrupted!"
642
638
643 if failed:
639 if failed:
644 sys.exit(1)
640 sys.exit(1)
645
641
646 def main():
642 def main():
647 (options, args) = parseargs()
643 (options, args) = parseargs()
648 if not options.child:
644 if not options.child:
649 os.umask(022)
645 os.umask(022)
650
646
651 checktools()
647 checktools()
652
648
653 # Reset some environment variables to well-known values so that
649 # Reset some environment variables to well-known values so that
654 # the tests produce repeatable output.
650 # the tests produce repeatable output.
655 os.environ['LANG'] = os.environ['LC_ALL'] = 'C'
651 os.environ['LANG'] = os.environ['LC_ALL'] = 'C'
656 os.environ['TZ'] = 'GMT'
652 os.environ['TZ'] = 'GMT'
657 os.environ["EMAIL"] = "Foo Bar <foo.bar@example.com>"
653 os.environ["EMAIL"] = "Foo Bar <foo.bar@example.com>"
658 os.environ['CDPATH'] = ''
654 os.environ['CDPATH'] = ''
659
655
660 global TESTDIR, HGTMP, INST, BINDIR, PYTHONDIR, COVERAGE_FILE
656 global TESTDIR, HGTMP, INST, BINDIR, PYTHONDIR, COVERAGE_FILE
661 TESTDIR = os.environ["TESTDIR"] = os.getcwd()
657 TESTDIR = os.environ["TESTDIR"] = os.getcwd()
662 HGTMP = os.environ['HGTMP'] = os.path.realpath(tempfile.mkdtemp('', 'hgtests.',
658 HGTMP = os.environ['HGTMP'] = os.path.realpath(tempfile.mkdtemp('', 'hgtests.',
663 options.tmpdir))
659 options.tmpdir))
664 DAEMON_PIDS = None
660 DAEMON_PIDS = None
665 HGRCPATH = None
661 HGRCPATH = None
666
662
667 os.environ["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
663 os.environ["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
668 os.environ["HGMERGE"] = "internal:merge"
664 os.environ["HGMERGE"] = "internal:merge"
669 os.environ["HGUSER"] = "test"
665 os.environ["HGUSER"] = "test"
670 os.environ["HGENCODING"] = "ascii"
666 os.environ["HGENCODING"] = "ascii"
671 os.environ["HGENCODINGMODE"] = "strict"
667 os.environ["HGENCODINGMODE"] = "strict"
672 os.environ["HGPORT"] = str(options.port)
668 os.environ["HGPORT"] = str(options.port)
673 os.environ["HGPORT1"] = str(options.port + 1)
669 os.environ["HGPORT1"] = str(options.port + 1)
674 os.environ["HGPORT2"] = str(options.port + 2)
670 os.environ["HGPORT2"] = str(options.port + 2)
675
671
676 if options.with_hg:
672 if options.with_hg:
677 INST = options.with_hg
673 INST = options.with_hg
678 else:
674 else:
679 INST = os.path.join(HGTMP, "install")
675 INST = os.path.join(HGTMP, "install")
680 BINDIR = os.environ["BINDIR"] = os.path.join(INST, "bin")
676 BINDIR = os.environ["BINDIR"] = os.path.join(INST, "bin")
681 PYTHONDIR = os.path.join(INST, "lib", "python")
677 PYTHONDIR = os.path.join(INST, "lib", "python")
682 COVERAGE_FILE = os.path.join(TESTDIR, ".coverage")
678 COVERAGE_FILE = os.path.join(TESTDIR, ".coverage")
683
679
684 expecthg = os.path.join(HGTMP, 'install', 'lib', 'python', 'mercurial')
680 expecthg = os.path.join(HGTMP, 'install', 'lib', 'python', 'mercurial')
685
681
686 if len(args) == 0:
682 if len(args) == 0:
687 args = os.listdir(".")
683 args = os.listdir(".")
688 args.sort()
684 args.sort()
689
685
690 tests = []
686 tests = []
691 for test in args:
687 for test in args:
692 if (test.startswith("test-") and '~' not in test and
688 if (test.startswith("test-") and '~' not in test and
693 ('.' not in test or test.endswith('.py') or
689 ('.' not in test or test.endswith('.py') or
694 test.endswith('.bat'))):
690 test.endswith('.bat'))):
695 tests.append(test)
691 tests.append(test)
696
692
697 vlog("# Using TESTDIR", TESTDIR)
693 vlog("# Using TESTDIR", TESTDIR)
698 vlog("# Using HGTMP", HGTMP)
694 vlog("# Using HGTMP", HGTMP)
699
695
700 try:
696 try:
701 if len(tests) > 1 and options.jobs > 1:
697 if len(tests) > 1 and options.jobs > 1:
702 runchildren(options, expecthg, tests)
698 runchildren(options, expecthg, tests)
703 else:
699 else:
704 runtests(options, expecthg, tests)
700 runtests(options, expecthg, tests)
705 finally:
701 finally:
706 cleanup(options)
702 cleanup(options)
707
703
708 main()
704 main()
General Comments 0
You need to be logged in to leave comments. Login now