##// END OF EJS Templates
better handle errors with date parsing (issue983)...
Dirkjan Ochtman -
r6087:12856a17 default
parent child Browse files
Show More
@@ -1,1736 +1,1736 b''
1 """
1 """
2 util.py - Mercurial utility functions and platform specfic implementations
2 util.py - Mercurial utility functions and platform specfic implementations
3
3
4 Copyright 2005 K. Thananchayan <thananck@yahoo.com>
4 Copyright 2005 K. Thananchayan <thananck@yahoo.com>
5 Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
6 Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
6 Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
7
7
8 This software may be used and distributed according to the terms
8 This software may be used and distributed according to the terms
9 of the GNU General Public License, incorporated herein by reference.
9 of the GNU General Public License, incorporated herein by reference.
10
10
11 This contains helper routines that are independent of the SCM core and hide
11 This contains helper routines that are independent of the SCM core and hide
12 platform-specific details from the core.
12 platform-specific details from the core.
13 """
13 """
14
14
15 from i18n import _
15 from i18n import _
16 import cStringIO, errno, getpass, popen2, re, shutil, sys, tempfile, strutil
16 import cStringIO, errno, getpass, popen2, re, shutil, sys, tempfile, strutil
17 import os, stat, threading, time, calendar, ConfigParser, locale, glob, osutil
17 import os, stat, threading, time, calendar, ConfigParser, locale, glob, osutil
18 import urlparse
18 import urlparse
19
19
20 try:
20 try:
21 set = set
21 set = set
22 frozenset = frozenset
22 frozenset = frozenset
23 except NameError:
23 except NameError:
24 from sets import Set as set, ImmutableSet as frozenset
24 from sets import Set as set, ImmutableSet as frozenset
25
25
26 try:
26 try:
27 _encoding = os.environ.get("HGENCODING")
27 _encoding = os.environ.get("HGENCODING")
28 if sys.platform == 'darwin' and not _encoding:
28 if sys.platform == 'darwin' and not _encoding:
29 # On darwin, getpreferredencoding ignores the locale environment and
29 # On darwin, getpreferredencoding ignores the locale environment and
30 # always returns mac-roman. We override this if the environment is
30 # always returns mac-roman. We override this if the environment is
31 # not C (has been customized by the user).
31 # not C (has been customized by the user).
32 locale.setlocale(locale.LC_CTYPE, '')
32 locale.setlocale(locale.LC_CTYPE, '')
33 _encoding = locale.getlocale()[1]
33 _encoding = locale.getlocale()[1]
34 if not _encoding:
34 if not _encoding:
35 _encoding = locale.getpreferredencoding() or 'ascii'
35 _encoding = locale.getpreferredencoding() or 'ascii'
36 except locale.Error:
36 except locale.Error:
37 _encoding = 'ascii'
37 _encoding = 'ascii'
38 _encodingmode = os.environ.get("HGENCODINGMODE", "strict")
38 _encodingmode = os.environ.get("HGENCODINGMODE", "strict")
39 _fallbackencoding = 'ISO-8859-1'
39 _fallbackencoding = 'ISO-8859-1'
40
40
41 def tolocal(s):
41 def tolocal(s):
42 """
42 """
43 Convert a string from internal UTF-8 to local encoding
43 Convert a string from internal UTF-8 to local encoding
44
44
45 All internal strings should be UTF-8 but some repos before the
45 All internal strings should be UTF-8 but some repos before the
46 implementation of locale support may contain latin1 or possibly
46 implementation of locale support may contain latin1 or possibly
47 other character sets. We attempt to decode everything strictly
47 other character sets. We attempt to decode everything strictly
48 using UTF-8, then Latin-1, and failing that, we use UTF-8 and
48 using UTF-8, then Latin-1, and failing that, we use UTF-8 and
49 replace unknown characters.
49 replace unknown characters.
50 """
50 """
51 for e in ('UTF-8', _fallbackencoding):
51 for e in ('UTF-8', _fallbackencoding):
52 try:
52 try:
53 u = s.decode(e) # attempt strict decoding
53 u = s.decode(e) # attempt strict decoding
54 return u.encode(_encoding, "replace")
54 return u.encode(_encoding, "replace")
55 except LookupError, k:
55 except LookupError, k:
56 raise Abort(_("%s, please check your locale settings") % k)
56 raise Abort(_("%s, please check your locale settings") % k)
57 except UnicodeDecodeError:
57 except UnicodeDecodeError:
58 pass
58 pass
59 u = s.decode("utf-8", "replace") # last ditch
59 u = s.decode("utf-8", "replace") # last ditch
60 return u.encode(_encoding, "replace")
60 return u.encode(_encoding, "replace")
61
61
62 def fromlocal(s):
62 def fromlocal(s):
63 """
63 """
64 Convert a string from the local character encoding to UTF-8
64 Convert a string from the local character encoding to UTF-8
65
65
66 We attempt to decode strings using the encoding mode set by
66 We attempt to decode strings using the encoding mode set by
67 HGENCODINGMODE, which defaults to 'strict'. In this mode, unknown
67 HGENCODINGMODE, which defaults to 'strict'. In this mode, unknown
68 characters will cause an error message. Other modes include
68 characters will cause an error message. Other modes include
69 'replace', which replaces unknown characters with a special
69 'replace', which replaces unknown characters with a special
70 Unicode character, and 'ignore', which drops the character.
70 Unicode character, and 'ignore', which drops the character.
71 """
71 """
72 try:
72 try:
73 return s.decode(_encoding, _encodingmode).encode("utf-8")
73 return s.decode(_encoding, _encodingmode).encode("utf-8")
74 except UnicodeDecodeError, inst:
74 except UnicodeDecodeError, inst:
75 sub = s[max(0, inst.start-10):inst.start+10]
75 sub = s[max(0, inst.start-10):inst.start+10]
76 raise Abort("decoding near '%s': %s!" % (sub, inst))
76 raise Abort("decoding near '%s': %s!" % (sub, inst))
77 except LookupError, k:
77 except LookupError, k:
78 raise Abort(_("%s, please check your locale settings") % k)
78 raise Abort(_("%s, please check your locale settings") % k)
79
79
80 def locallen(s):
80 def locallen(s):
81 """Find the length in characters of a local string"""
81 """Find the length in characters of a local string"""
82 return len(s.decode(_encoding, "replace"))
82 return len(s.decode(_encoding, "replace"))
83
83
84 def localsub(s, a, b=None):
84 def localsub(s, a, b=None):
85 try:
85 try:
86 u = s.decode(_encoding, _encodingmode)
86 u = s.decode(_encoding, _encodingmode)
87 if b is not None:
87 if b is not None:
88 u = u[a:b]
88 u = u[a:b]
89 else:
89 else:
90 u = u[:a]
90 u = u[:a]
91 return u.encode(_encoding, _encodingmode)
91 return u.encode(_encoding, _encodingmode)
92 except UnicodeDecodeError, inst:
92 except UnicodeDecodeError, inst:
93 sub = s[max(0, inst.start-10), inst.start+10]
93 sub = s[max(0, inst.start-10), inst.start+10]
94 raise Abort(_("decoding near '%s': %s!") % (sub, inst))
94 raise Abort(_("decoding near '%s': %s!") % (sub, inst))
95
95
96 # used by parsedate
96 # used by parsedate
97 defaultdateformats = (
97 defaultdateformats = (
98 '%Y-%m-%d %H:%M:%S',
98 '%Y-%m-%d %H:%M:%S',
99 '%Y-%m-%d %I:%M:%S%p',
99 '%Y-%m-%d %I:%M:%S%p',
100 '%Y-%m-%d %H:%M',
100 '%Y-%m-%d %H:%M',
101 '%Y-%m-%d %I:%M%p',
101 '%Y-%m-%d %I:%M%p',
102 '%Y-%m-%d',
102 '%Y-%m-%d',
103 '%m-%d',
103 '%m-%d',
104 '%m/%d',
104 '%m/%d',
105 '%m/%d/%y',
105 '%m/%d/%y',
106 '%m/%d/%Y',
106 '%m/%d/%Y',
107 '%a %b %d %H:%M:%S %Y',
107 '%a %b %d %H:%M:%S %Y',
108 '%a %b %d %I:%M:%S%p %Y',
108 '%a %b %d %I:%M:%S%p %Y',
109 '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
109 '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
110 '%b %d %H:%M:%S %Y',
110 '%b %d %H:%M:%S %Y',
111 '%b %d %I:%M:%S%p %Y',
111 '%b %d %I:%M:%S%p %Y',
112 '%b %d %H:%M:%S',
112 '%b %d %H:%M:%S',
113 '%b %d %I:%M:%S%p',
113 '%b %d %I:%M:%S%p',
114 '%b %d %H:%M',
114 '%b %d %H:%M',
115 '%b %d %I:%M%p',
115 '%b %d %I:%M%p',
116 '%b %d %Y',
116 '%b %d %Y',
117 '%b %d',
117 '%b %d',
118 '%H:%M:%S',
118 '%H:%M:%S',
119 '%I:%M:%SP',
119 '%I:%M:%SP',
120 '%H:%M',
120 '%H:%M',
121 '%I:%M%p',
121 '%I:%M%p',
122 )
122 )
123
123
124 extendeddateformats = defaultdateformats + (
124 extendeddateformats = defaultdateformats + (
125 "%Y",
125 "%Y",
126 "%Y-%m",
126 "%Y-%m",
127 "%b",
127 "%b",
128 "%b %Y",
128 "%b %Y",
129 )
129 )
130
130
131 class SignalInterrupt(Exception):
131 class SignalInterrupt(Exception):
132 """Exception raised on SIGTERM and SIGHUP."""
132 """Exception raised on SIGTERM and SIGHUP."""
133
133
134 # differences from SafeConfigParser:
134 # differences from SafeConfigParser:
135 # - case-sensitive keys
135 # - case-sensitive keys
136 # - allows values that are not strings (this means that you may not
136 # - allows values that are not strings (this means that you may not
137 # be able to save the configuration to a file)
137 # be able to save the configuration to a file)
138 class configparser(ConfigParser.SafeConfigParser):
138 class configparser(ConfigParser.SafeConfigParser):
139 def optionxform(self, optionstr):
139 def optionxform(self, optionstr):
140 return optionstr
140 return optionstr
141
141
142 def set(self, section, option, value):
142 def set(self, section, option, value):
143 return ConfigParser.ConfigParser.set(self, section, option, value)
143 return ConfigParser.ConfigParser.set(self, section, option, value)
144
144
145 def _interpolate(self, section, option, rawval, vars):
145 def _interpolate(self, section, option, rawval, vars):
146 if not isinstance(rawval, basestring):
146 if not isinstance(rawval, basestring):
147 return rawval
147 return rawval
148 return ConfigParser.SafeConfigParser._interpolate(self, section,
148 return ConfigParser.SafeConfigParser._interpolate(self, section,
149 option, rawval, vars)
149 option, rawval, vars)
150
150
151 def cachefunc(func):
151 def cachefunc(func):
152 '''cache the result of function calls'''
152 '''cache the result of function calls'''
153 # XXX doesn't handle keywords args
153 # XXX doesn't handle keywords args
154 cache = {}
154 cache = {}
155 if func.func_code.co_argcount == 1:
155 if func.func_code.co_argcount == 1:
156 # we gain a small amount of time because
156 # we gain a small amount of time because
157 # we don't need to pack/unpack the list
157 # we don't need to pack/unpack the list
158 def f(arg):
158 def f(arg):
159 if arg not in cache:
159 if arg not in cache:
160 cache[arg] = func(arg)
160 cache[arg] = func(arg)
161 return cache[arg]
161 return cache[arg]
162 else:
162 else:
163 def f(*args):
163 def f(*args):
164 if args not in cache:
164 if args not in cache:
165 cache[args] = func(*args)
165 cache[args] = func(*args)
166 return cache[args]
166 return cache[args]
167
167
168 return f
168 return f
169
169
170 def pipefilter(s, cmd):
170 def pipefilter(s, cmd):
171 '''filter string S through command CMD, returning its output'''
171 '''filter string S through command CMD, returning its output'''
172 (pin, pout) = os.popen2(cmd, 'b')
172 (pin, pout) = os.popen2(cmd, 'b')
173 def writer():
173 def writer():
174 try:
174 try:
175 pin.write(s)
175 pin.write(s)
176 pin.close()
176 pin.close()
177 except IOError, inst:
177 except IOError, inst:
178 if inst.errno != errno.EPIPE:
178 if inst.errno != errno.EPIPE:
179 raise
179 raise
180
180
181 # we should use select instead on UNIX, but this will work on most
181 # we should use select instead on UNIX, but this will work on most
182 # systems, including Windows
182 # systems, including Windows
183 w = threading.Thread(target=writer)
183 w = threading.Thread(target=writer)
184 w.start()
184 w.start()
185 f = pout.read()
185 f = pout.read()
186 pout.close()
186 pout.close()
187 w.join()
187 w.join()
188 return f
188 return f
189
189
190 def tempfilter(s, cmd):
190 def tempfilter(s, cmd):
191 '''filter string S through a pair of temporary files with CMD.
191 '''filter string S through a pair of temporary files with CMD.
192 CMD is used as a template to create the real command to be run,
192 CMD is used as a template to create the real command to be run,
193 with the strings INFILE and OUTFILE replaced by the real names of
193 with the strings INFILE and OUTFILE replaced by the real names of
194 the temporary files generated.'''
194 the temporary files generated.'''
195 inname, outname = None, None
195 inname, outname = None, None
196 try:
196 try:
197 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
197 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
198 fp = os.fdopen(infd, 'wb')
198 fp = os.fdopen(infd, 'wb')
199 fp.write(s)
199 fp.write(s)
200 fp.close()
200 fp.close()
201 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
201 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
202 os.close(outfd)
202 os.close(outfd)
203 cmd = cmd.replace('INFILE', inname)
203 cmd = cmd.replace('INFILE', inname)
204 cmd = cmd.replace('OUTFILE', outname)
204 cmd = cmd.replace('OUTFILE', outname)
205 code = os.system(cmd)
205 code = os.system(cmd)
206 if sys.platform == 'OpenVMS' and code & 1:
206 if sys.platform == 'OpenVMS' and code & 1:
207 code = 0
207 code = 0
208 if code: raise Abort(_("command '%s' failed: %s") %
208 if code: raise Abort(_("command '%s' failed: %s") %
209 (cmd, explain_exit(code)))
209 (cmd, explain_exit(code)))
210 return open(outname, 'rb').read()
210 return open(outname, 'rb').read()
211 finally:
211 finally:
212 try:
212 try:
213 if inname: os.unlink(inname)
213 if inname: os.unlink(inname)
214 except: pass
214 except: pass
215 try:
215 try:
216 if outname: os.unlink(outname)
216 if outname: os.unlink(outname)
217 except: pass
217 except: pass
218
218
219 filtertable = {
219 filtertable = {
220 'tempfile:': tempfilter,
220 'tempfile:': tempfilter,
221 'pipe:': pipefilter,
221 'pipe:': pipefilter,
222 }
222 }
223
223
224 def filter(s, cmd):
224 def filter(s, cmd):
225 "filter a string through a command that transforms its input to its output"
225 "filter a string through a command that transforms its input to its output"
226 for name, fn in filtertable.iteritems():
226 for name, fn in filtertable.iteritems():
227 if cmd.startswith(name):
227 if cmd.startswith(name):
228 return fn(s, cmd[len(name):].lstrip())
228 return fn(s, cmd[len(name):].lstrip())
229 return pipefilter(s, cmd)
229 return pipefilter(s, cmd)
230
230
231 def binary(s):
231 def binary(s):
232 """return true if a string is binary data using diff's heuristic"""
232 """return true if a string is binary data using diff's heuristic"""
233 if s and '\0' in s[:4096]:
233 if s and '\0' in s[:4096]:
234 return True
234 return True
235 return False
235 return False
236
236
237 def unique(g):
237 def unique(g):
238 """return the uniq elements of iterable g"""
238 """return the uniq elements of iterable g"""
239 return dict.fromkeys(g).keys()
239 return dict.fromkeys(g).keys()
240
240
241 class Abort(Exception):
241 class Abort(Exception):
242 """Raised if a command needs to print an error and exit."""
242 """Raised if a command needs to print an error and exit."""
243
243
244 class UnexpectedOutput(Abort):
244 class UnexpectedOutput(Abort):
245 """Raised to print an error with part of output and exit."""
245 """Raised to print an error with part of output and exit."""
246
246
247 def always(fn): return True
247 def always(fn): return True
248 def never(fn): return False
248 def never(fn): return False
249
249
250 def expand_glob(pats):
250 def expand_glob(pats):
251 '''On Windows, expand the implicit globs in a list of patterns'''
251 '''On Windows, expand the implicit globs in a list of patterns'''
252 if os.name != 'nt':
252 if os.name != 'nt':
253 return list(pats)
253 return list(pats)
254 ret = []
254 ret = []
255 for p in pats:
255 for p in pats:
256 kind, name = patkind(p, None)
256 kind, name = patkind(p, None)
257 if kind is None:
257 if kind is None:
258 globbed = glob.glob(name)
258 globbed = glob.glob(name)
259 if globbed:
259 if globbed:
260 ret.extend(globbed)
260 ret.extend(globbed)
261 continue
261 continue
262 # if we couldn't expand the glob, just keep it around
262 # if we couldn't expand the glob, just keep it around
263 ret.append(p)
263 ret.append(p)
264 return ret
264 return ret
265
265
266 def patkind(name, dflt_pat='glob'):
266 def patkind(name, dflt_pat='glob'):
267 """Split a string into an optional pattern kind prefix and the
267 """Split a string into an optional pattern kind prefix and the
268 actual pattern."""
268 actual pattern."""
269 for prefix in 're', 'glob', 'path', 'relglob', 'relpath', 'relre':
269 for prefix in 're', 'glob', 'path', 'relglob', 'relpath', 'relre':
270 if name.startswith(prefix + ':'): return name.split(':', 1)
270 if name.startswith(prefix + ':'): return name.split(':', 1)
271 return dflt_pat, name
271 return dflt_pat, name
272
272
273 def globre(pat, head='^', tail='$'):
273 def globre(pat, head='^', tail='$'):
274 "convert a glob pattern into a regexp"
274 "convert a glob pattern into a regexp"
275 i, n = 0, len(pat)
275 i, n = 0, len(pat)
276 res = ''
276 res = ''
277 group = False
277 group = False
278 def peek(): return i < n and pat[i]
278 def peek(): return i < n and pat[i]
279 while i < n:
279 while i < n:
280 c = pat[i]
280 c = pat[i]
281 i = i+1
281 i = i+1
282 if c == '*':
282 if c == '*':
283 if peek() == '*':
283 if peek() == '*':
284 i += 1
284 i += 1
285 res += '.*'
285 res += '.*'
286 else:
286 else:
287 res += '[^/]*'
287 res += '[^/]*'
288 elif c == '?':
288 elif c == '?':
289 res += '.'
289 res += '.'
290 elif c == '[':
290 elif c == '[':
291 j = i
291 j = i
292 if j < n and pat[j] in '!]':
292 if j < n and pat[j] in '!]':
293 j += 1
293 j += 1
294 while j < n and pat[j] != ']':
294 while j < n and pat[j] != ']':
295 j += 1
295 j += 1
296 if j >= n:
296 if j >= n:
297 res += '\\['
297 res += '\\['
298 else:
298 else:
299 stuff = pat[i:j].replace('\\','\\\\')
299 stuff = pat[i:j].replace('\\','\\\\')
300 i = j + 1
300 i = j + 1
301 if stuff[0] == '!':
301 if stuff[0] == '!':
302 stuff = '^' + stuff[1:]
302 stuff = '^' + stuff[1:]
303 elif stuff[0] == '^':
303 elif stuff[0] == '^':
304 stuff = '\\' + stuff
304 stuff = '\\' + stuff
305 res = '%s[%s]' % (res, stuff)
305 res = '%s[%s]' % (res, stuff)
306 elif c == '{':
306 elif c == '{':
307 group = True
307 group = True
308 res += '(?:'
308 res += '(?:'
309 elif c == '}' and group:
309 elif c == '}' and group:
310 res += ')'
310 res += ')'
311 group = False
311 group = False
312 elif c == ',' and group:
312 elif c == ',' and group:
313 res += '|'
313 res += '|'
314 elif c == '\\':
314 elif c == '\\':
315 p = peek()
315 p = peek()
316 if p:
316 if p:
317 i += 1
317 i += 1
318 res += re.escape(p)
318 res += re.escape(p)
319 else:
319 else:
320 res += re.escape(c)
320 res += re.escape(c)
321 else:
321 else:
322 res += re.escape(c)
322 res += re.escape(c)
323 return head + res + tail
323 return head + res + tail
324
324
325 _globchars = {'[': 1, '{': 1, '*': 1, '?': 1}
325 _globchars = {'[': 1, '{': 1, '*': 1, '?': 1}
326
326
327 def pathto(root, n1, n2):
327 def pathto(root, n1, n2):
328 '''return the relative path from one place to another.
328 '''return the relative path from one place to another.
329 root should use os.sep to separate directories
329 root should use os.sep to separate directories
330 n1 should use os.sep to separate directories
330 n1 should use os.sep to separate directories
331 n2 should use "/" to separate directories
331 n2 should use "/" to separate directories
332 returns an os.sep-separated path.
332 returns an os.sep-separated path.
333
333
334 If n1 is a relative path, it's assumed it's
334 If n1 is a relative path, it's assumed it's
335 relative to root.
335 relative to root.
336 n2 should always be relative to root.
336 n2 should always be relative to root.
337 '''
337 '''
338 if not n1: return localpath(n2)
338 if not n1: return localpath(n2)
339 if os.path.isabs(n1):
339 if os.path.isabs(n1):
340 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
340 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
341 return os.path.join(root, localpath(n2))
341 return os.path.join(root, localpath(n2))
342 n2 = '/'.join((pconvert(root), n2))
342 n2 = '/'.join((pconvert(root), n2))
343 a, b = n1.split(os.sep), n2.split('/')
343 a, b = n1.split(os.sep), n2.split('/')
344 a.reverse()
344 a.reverse()
345 b.reverse()
345 b.reverse()
346 while a and b and a[-1] == b[-1]:
346 while a and b and a[-1] == b[-1]:
347 a.pop()
347 a.pop()
348 b.pop()
348 b.pop()
349 b.reverse()
349 b.reverse()
350 return os.sep.join((['..'] * len(a)) + b)
350 return os.sep.join((['..'] * len(a)) + b)
351
351
352 def canonpath(root, cwd, myname):
352 def canonpath(root, cwd, myname):
353 """return the canonical path of myname, given cwd and root"""
353 """return the canonical path of myname, given cwd and root"""
354 if root == os.sep:
354 if root == os.sep:
355 rootsep = os.sep
355 rootsep = os.sep
356 elif root.endswith(os.sep):
356 elif root.endswith(os.sep):
357 rootsep = root
357 rootsep = root
358 else:
358 else:
359 rootsep = root + os.sep
359 rootsep = root + os.sep
360 name = myname
360 name = myname
361 if not os.path.isabs(name):
361 if not os.path.isabs(name):
362 name = os.path.join(root, cwd, name)
362 name = os.path.join(root, cwd, name)
363 name = os.path.normpath(name)
363 name = os.path.normpath(name)
364 audit_path = path_auditor(root)
364 audit_path = path_auditor(root)
365 if name != rootsep and name.startswith(rootsep):
365 if name != rootsep and name.startswith(rootsep):
366 name = name[len(rootsep):]
366 name = name[len(rootsep):]
367 audit_path(name)
367 audit_path(name)
368 return pconvert(name)
368 return pconvert(name)
369 elif name == root:
369 elif name == root:
370 return ''
370 return ''
371 else:
371 else:
372 # Determine whether `name' is in the hierarchy at or beneath `root',
372 # Determine whether `name' is in the hierarchy at or beneath `root',
373 # by iterating name=dirname(name) until that causes no change (can't
373 # by iterating name=dirname(name) until that causes no change (can't
374 # check name == '/', because that doesn't work on windows). For each
374 # check name == '/', because that doesn't work on windows). For each
375 # `name', compare dev/inode numbers. If they match, the list `rel'
375 # `name', compare dev/inode numbers. If they match, the list `rel'
376 # holds the reversed list of components making up the relative file
376 # holds the reversed list of components making up the relative file
377 # name we want.
377 # name we want.
378 root_st = os.stat(root)
378 root_st = os.stat(root)
379 rel = []
379 rel = []
380 while True:
380 while True:
381 try:
381 try:
382 name_st = os.stat(name)
382 name_st = os.stat(name)
383 except OSError:
383 except OSError:
384 break
384 break
385 if samestat(name_st, root_st):
385 if samestat(name_st, root_st):
386 if not rel:
386 if not rel:
387 # name was actually the same as root (maybe a symlink)
387 # name was actually the same as root (maybe a symlink)
388 return ''
388 return ''
389 rel.reverse()
389 rel.reverse()
390 name = os.path.join(*rel)
390 name = os.path.join(*rel)
391 audit_path(name)
391 audit_path(name)
392 return pconvert(name)
392 return pconvert(name)
393 dirname, basename = os.path.split(name)
393 dirname, basename = os.path.split(name)
394 rel.append(basename)
394 rel.append(basename)
395 if dirname == name:
395 if dirname == name:
396 break
396 break
397 name = dirname
397 name = dirname
398
398
399 raise Abort('%s not under root' % myname)
399 raise Abort('%s not under root' % myname)
400
400
401 def matcher(canonroot, cwd='', names=[], inc=[], exc=[], src=None):
401 def matcher(canonroot, cwd='', names=[], inc=[], exc=[], src=None):
402 return _matcher(canonroot, cwd, names, inc, exc, 'glob', src)
402 return _matcher(canonroot, cwd, names, inc, exc, 'glob', src)
403
403
404 def cmdmatcher(canonroot, cwd='', names=[], inc=[], exc=[], src=None,
404 def cmdmatcher(canonroot, cwd='', names=[], inc=[], exc=[], src=None,
405 globbed=False, default=None):
405 globbed=False, default=None):
406 default = default or 'relpath'
406 default = default or 'relpath'
407 if default == 'relpath' and not globbed:
407 if default == 'relpath' and not globbed:
408 names = expand_glob(names)
408 names = expand_glob(names)
409 return _matcher(canonroot, cwd, names, inc, exc, default, src)
409 return _matcher(canonroot, cwd, names, inc, exc, default, src)
410
410
411 def _matcher(canonroot, cwd, names, inc, exc, dflt_pat, src):
411 def _matcher(canonroot, cwd, names, inc, exc, dflt_pat, src):
412 """build a function to match a set of file patterns
412 """build a function to match a set of file patterns
413
413
414 arguments:
414 arguments:
415 canonroot - the canonical root of the tree you're matching against
415 canonroot - the canonical root of the tree you're matching against
416 cwd - the current working directory, if relevant
416 cwd - the current working directory, if relevant
417 names - patterns to find
417 names - patterns to find
418 inc - patterns to include
418 inc - patterns to include
419 exc - patterns to exclude
419 exc - patterns to exclude
420 dflt_pat - if a pattern in names has no explicit type, assume this one
420 dflt_pat - if a pattern in names has no explicit type, assume this one
421 src - where these patterns came from (e.g. .hgignore)
421 src - where these patterns came from (e.g. .hgignore)
422
422
423 a pattern is one of:
423 a pattern is one of:
424 'glob:<glob>' - a glob relative to cwd
424 'glob:<glob>' - a glob relative to cwd
425 're:<regexp>' - a regular expression
425 're:<regexp>' - a regular expression
426 'path:<path>' - a path relative to canonroot
426 'path:<path>' - a path relative to canonroot
427 'relglob:<glob>' - an unrooted glob (*.c matches C files in all dirs)
427 'relglob:<glob>' - an unrooted glob (*.c matches C files in all dirs)
428 'relpath:<path>' - a path relative to cwd
428 'relpath:<path>' - a path relative to cwd
429 'relre:<regexp>' - a regexp that doesn't have to match the start of a name
429 'relre:<regexp>' - a regexp that doesn't have to match the start of a name
430 '<something>' - one of the cases above, selected by the dflt_pat argument
430 '<something>' - one of the cases above, selected by the dflt_pat argument
431
431
432 returns:
432 returns:
433 a 3-tuple containing
433 a 3-tuple containing
434 - list of roots (places where one should start a recursive walk of the fs);
434 - list of roots (places where one should start a recursive walk of the fs);
435 this often matches the explicit non-pattern names passed in, but also
435 this often matches the explicit non-pattern names passed in, but also
436 includes the initial part of glob: patterns that has no glob characters
436 includes the initial part of glob: patterns that has no glob characters
437 - a bool match(filename) function
437 - a bool match(filename) function
438 - a bool indicating if any patterns were passed in
438 - a bool indicating if any patterns were passed in
439 """
439 """
440
440
441 # a common case: no patterns at all
441 # a common case: no patterns at all
442 if not names and not inc and not exc:
442 if not names and not inc and not exc:
443 return [], always, False
443 return [], always, False
444
444
445 def contains_glob(name):
445 def contains_glob(name):
446 for c in name:
446 for c in name:
447 if c in _globchars: return True
447 if c in _globchars: return True
448 return False
448 return False
449
449
450 def regex(kind, name, tail):
450 def regex(kind, name, tail):
451 '''convert a pattern into a regular expression'''
451 '''convert a pattern into a regular expression'''
452 if not name:
452 if not name:
453 return ''
453 return ''
454 if kind == 're':
454 if kind == 're':
455 return name
455 return name
456 elif kind == 'path':
456 elif kind == 'path':
457 return '^' + re.escape(name) + '(?:/|$)'
457 return '^' + re.escape(name) + '(?:/|$)'
458 elif kind == 'relglob':
458 elif kind == 'relglob':
459 return globre(name, '(?:|.*/)', tail)
459 return globre(name, '(?:|.*/)', tail)
460 elif kind == 'relpath':
460 elif kind == 'relpath':
461 return re.escape(name) + '(?:/|$)'
461 return re.escape(name) + '(?:/|$)'
462 elif kind == 'relre':
462 elif kind == 'relre':
463 if name.startswith('^'):
463 if name.startswith('^'):
464 return name
464 return name
465 return '.*' + name
465 return '.*' + name
466 return globre(name, '', tail)
466 return globre(name, '', tail)
467
467
468 def matchfn(pats, tail):
468 def matchfn(pats, tail):
469 """build a matching function from a set of patterns"""
469 """build a matching function from a set of patterns"""
470 if not pats:
470 if not pats:
471 return
471 return
472 try:
472 try:
473 pat = '(?:%s)' % '|'.join([regex(k, p, tail) for (k, p) in pats])
473 pat = '(?:%s)' % '|'.join([regex(k, p, tail) for (k, p) in pats])
474 return re.compile(pat).match
474 return re.compile(pat).match
475 except OverflowError:
475 except OverflowError:
476 # We're using a Python with a tiny regex engine and we
476 # We're using a Python with a tiny regex engine and we
477 # made it explode, so we'll divide the pattern list in two
477 # made it explode, so we'll divide the pattern list in two
478 # until it works
478 # until it works
479 l = len(pats)
479 l = len(pats)
480 if l < 2:
480 if l < 2:
481 raise
481 raise
482 a, b = matchfn(pats[:l//2], tail), matchfn(pats[l//2:], tail)
482 a, b = matchfn(pats[:l//2], tail), matchfn(pats[l//2:], tail)
483 return lambda s: a(s) or b(s)
483 return lambda s: a(s) or b(s)
484 except re.error:
484 except re.error:
485 for k, p in pats:
485 for k, p in pats:
486 try:
486 try:
487 re.compile('(?:%s)' % regex(k, p, tail))
487 re.compile('(?:%s)' % regex(k, p, tail))
488 except re.error:
488 except re.error:
489 if src:
489 if src:
490 raise Abort("%s: invalid pattern (%s): %s" %
490 raise Abort("%s: invalid pattern (%s): %s" %
491 (src, k, p))
491 (src, k, p))
492 else:
492 else:
493 raise Abort("invalid pattern (%s): %s" % (k, p))
493 raise Abort("invalid pattern (%s): %s" % (k, p))
494 raise Abort("invalid pattern")
494 raise Abort("invalid pattern")
495
495
496 def globprefix(pat):
496 def globprefix(pat):
497 '''return the non-glob prefix of a path, e.g. foo/* -> foo'''
497 '''return the non-glob prefix of a path, e.g. foo/* -> foo'''
498 root = []
498 root = []
499 for p in pat.split('/'):
499 for p in pat.split('/'):
500 if contains_glob(p): break
500 if contains_glob(p): break
501 root.append(p)
501 root.append(p)
502 return '/'.join(root) or '.'
502 return '/'.join(root) or '.'
503
503
504 def normalizepats(names, default):
504 def normalizepats(names, default):
505 pats = []
505 pats = []
506 roots = []
506 roots = []
507 anypats = False
507 anypats = False
508 for kind, name in [patkind(p, default) for p in names]:
508 for kind, name in [patkind(p, default) for p in names]:
509 if kind in ('glob', 'relpath'):
509 if kind in ('glob', 'relpath'):
510 name = canonpath(canonroot, cwd, name)
510 name = canonpath(canonroot, cwd, name)
511 elif kind in ('relglob', 'path'):
511 elif kind in ('relglob', 'path'):
512 name = normpath(name)
512 name = normpath(name)
513
513
514 pats.append((kind, name))
514 pats.append((kind, name))
515
515
516 if kind in ('glob', 're', 'relglob', 'relre'):
516 if kind in ('glob', 're', 'relglob', 'relre'):
517 anypats = True
517 anypats = True
518
518
519 if kind == 'glob':
519 if kind == 'glob':
520 root = globprefix(name)
520 root = globprefix(name)
521 roots.append(root)
521 roots.append(root)
522 elif kind in ('relpath', 'path'):
522 elif kind in ('relpath', 'path'):
523 roots.append(name or '.')
523 roots.append(name or '.')
524 elif kind == 'relglob':
524 elif kind == 'relglob':
525 roots.append('.')
525 roots.append('.')
526 return roots, pats, anypats
526 return roots, pats, anypats
527
527
528 roots, pats, anypats = normalizepats(names, dflt_pat)
528 roots, pats, anypats = normalizepats(names, dflt_pat)
529
529
530 patmatch = matchfn(pats, '$') or always
530 patmatch = matchfn(pats, '$') or always
531 incmatch = always
531 incmatch = always
532 if inc:
532 if inc:
533 dummy, inckinds, dummy = normalizepats(inc, 'glob')
533 dummy, inckinds, dummy = normalizepats(inc, 'glob')
534 incmatch = matchfn(inckinds, '(?:/|$)')
534 incmatch = matchfn(inckinds, '(?:/|$)')
535 excmatch = lambda fn: False
535 excmatch = lambda fn: False
536 if exc:
536 if exc:
537 dummy, exckinds, dummy = normalizepats(exc, 'glob')
537 dummy, exckinds, dummy = normalizepats(exc, 'glob')
538 excmatch = matchfn(exckinds, '(?:/|$)')
538 excmatch = matchfn(exckinds, '(?:/|$)')
539
539
540 if not names and inc and not exc:
540 if not names and inc and not exc:
541 # common case: hgignore patterns
541 # common case: hgignore patterns
542 match = incmatch
542 match = incmatch
543 else:
543 else:
544 match = lambda fn: incmatch(fn) and not excmatch(fn) and patmatch(fn)
544 match = lambda fn: incmatch(fn) and not excmatch(fn) and patmatch(fn)
545
545
546 return (roots, match, (inc or exc or anypats) and True)
546 return (roots, match, (inc or exc or anypats) and True)
547
547
548 _hgexecutable = None
548 _hgexecutable = None
549
549
550 def hgexecutable():
550 def hgexecutable():
551 """return location of the 'hg' executable.
551 """return location of the 'hg' executable.
552
552
553 Defaults to $HG or 'hg' in the search path.
553 Defaults to $HG or 'hg' in the search path.
554 """
554 """
555 if _hgexecutable is None:
555 if _hgexecutable is None:
556 set_hgexecutable(os.environ.get('HG') or find_exe('hg', 'hg'))
556 set_hgexecutable(os.environ.get('HG') or find_exe('hg', 'hg'))
557 return _hgexecutable
557 return _hgexecutable
558
558
559 def set_hgexecutable(path):
559 def set_hgexecutable(path):
560 """set location of the 'hg' executable"""
560 """set location of the 'hg' executable"""
561 global _hgexecutable
561 global _hgexecutable
562 _hgexecutable = path
562 _hgexecutable = path
563
563
564 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None):
564 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None):
565 '''enhanced shell command execution.
565 '''enhanced shell command execution.
566 run with environment maybe modified, maybe in different dir.
566 run with environment maybe modified, maybe in different dir.
567
567
568 if command fails and onerr is None, return status. if ui object,
568 if command fails and onerr is None, return status. if ui object,
569 print error message and return status, else raise onerr object as
569 print error message and return status, else raise onerr object as
570 exception.'''
570 exception.'''
571 def py2shell(val):
571 def py2shell(val):
572 'convert python object into string that is useful to shell'
572 'convert python object into string that is useful to shell'
573 if val in (None, False):
573 if val in (None, False):
574 return '0'
574 return '0'
575 if val == True:
575 if val == True:
576 return '1'
576 return '1'
577 return str(val)
577 return str(val)
578 oldenv = {}
578 oldenv = {}
579 for k in environ:
579 for k in environ:
580 oldenv[k] = os.environ.get(k)
580 oldenv[k] = os.environ.get(k)
581 if cwd is not None:
581 if cwd is not None:
582 oldcwd = os.getcwd()
582 oldcwd = os.getcwd()
583 origcmd = cmd
583 origcmd = cmd
584 if os.name == 'nt':
584 if os.name == 'nt':
585 cmd = '"%s"' % cmd
585 cmd = '"%s"' % cmd
586 try:
586 try:
587 for k, v in environ.iteritems():
587 for k, v in environ.iteritems():
588 os.environ[k] = py2shell(v)
588 os.environ[k] = py2shell(v)
589 os.environ['HG'] = hgexecutable()
589 os.environ['HG'] = hgexecutable()
590 if cwd is not None and oldcwd != cwd:
590 if cwd is not None and oldcwd != cwd:
591 os.chdir(cwd)
591 os.chdir(cwd)
592 rc = os.system(cmd)
592 rc = os.system(cmd)
593 if sys.platform == 'OpenVMS' and rc & 1:
593 if sys.platform == 'OpenVMS' and rc & 1:
594 rc = 0
594 rc = 0
595 if rc and onerr:
595 if rc and onerr:
596 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
596 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
597 explain_exit(rc)[0])
597 explain_exit(rc)[0])
598 if errprefix:
598 if errprefix:
599 errmsg = '%s: %s' % (errprefix, errmsg)
599 errmsg = '%s: %s' % (errprefix, errmsg)
600 try:
600 try:
601 onerr.warn(errmsg + '\n')
601 onerr.warn(errmsg + '\n')
602 except AttributeError:
602 except AttributeError:
603 raise onerr(errmsg)
603 raise onerr(errmsg)
604 return rc
604 return rc
605 finally:
605 finally:
606 for k, v in oldenv.iteritems():
606 for k, v in oldenv.iteritems():
607 if v is None:
607 if v is None:
608 del os.environ[k]
608 del os.environ[k]
609 else:
609 else:
610 os.environ[k] = v
610 os.environ[k] = v
611 if cwd is not None and oldcwd != cwd:
611 if cwd is not None and oldcwd != cwd:
612 os.chdir(oldcwd)
612 os.chdir(oldcwd)
613
613
614 # os.path.lexists is not available on python2.3
614 # os.path.lexists is not available on python2.3
615 def lexists(filename):
615 def lexists(filename):
616 "test whether a file with this name exists. does not follow symlinks"
616 "test whether a file with this name exists. does not follow symlinks"
617 try:
617 try:
618 os.lstat(filename)
618 os.lstat(filename)
619 except:
619 except:
620 return False
620 return False
621 return True
621 return True
622
622
623 def rename(src, dst):
623 def rename(src, dst):
624 """forcibly rename a file"""
624 """forcibly rename a file"""
625 try:
625 try:
626 os.rename(src, dst)
626 os.rename(src, dst)
627 except OSError, err: # FIXME: check err (EEXIST ?)
627 except OSError, err: # FIXME: check err (EEXIST ?)
628 # on windows, rename to existing file is not allowed, so we
628 # on windows, rename to existing file is not allowed, so we
629 # must delete destination first. but if file is open, unlink
629 # must delete destination first. but if file is open, unlink
630 # schedules it for delete but does not delete it. rename
630 # schedules it for delete but does not delete it. rename
631 # happens immediately even for open files, so we create
631 # happens immediately even for open files, so we create
632 # temporary file, delete it, rename destination to that name,
632 # temporary file, delete it, rename destination to that name,
633 # then delete that. then rename is safe to do.
633 # then delete that. then rename is safe to do.
634 fd, temp = tempfile.mkstemp(dir=os.path.dirname(dst) or '.')
634 fd, temp = tempfile.mkstemp(dir=os.path.dirname(dst) or '.')
635 os.close(fd)
635 os.close(fd)
636 os.unlink(temp)
636 os.unlink(temp)
637 os.rename(dst, temp)
637 os.rename(dst, temp)
638 os.unlink(temp)
638 os.unlink(temp)
639 os.rename(src, dst)
639 os.rename(src, dst)
640
640
641 def unlink(f):
641 def unlink(f):
642 """unlink and remove the directory if it is empty"""
642 """unlink and remove the directory if it is empty"""
643 os.unlink(f)
643 os.unlink(f)
644 # try removing directories that might now be empty
644 # try removing directories that might now be empty
645 try:
645 try:
646 os.removedirs(os.path.dirname(f))
646 os.removedirs(os.path.dirname(f))
647 except OSError:
647 except OSError:
648 pass
648 pass
649
649
650 def copyfile(src, dest):
650 def copyfile(src, dest):
651 "copy a file, preserving mode"
651 "copy a file, preserving mode"
652 if os.path.islink(src):
652 if os.path.islink(src):
653 try:
653 try:
654 os.unlink(dest)
654 os.unlink(dest)
655 except:
655 except:
656 pass
656 pass
657 os.symlink(os.readlink(src), dest)
657 os.symlink(os.readlink(src), dest)
658 else:
658 else:
659 try:
659 try:
660 shutil.copyfile(src, dest)
660 shutil.copyfile(src, dest)
661 shutil.copymode(src, dest)
661 shutil.copymode(src, dest)
662 except shutil.Error, inst:
662 except shutil.Error, inst:
663 raise Abort(str(inst))
663 raise Abort(str(inst))
664
664
665 def copyfiles(src, dst, hardlink=None):
665 def copyfiles(src, dst, hardlink=None):
666 """Copy a directory tree using hardlinks if possible"""
666 """Copy a directory tree using hardlinks if possible"""
667
667
668 if hardlink is None:
668 if hardlink is None:
669 hardlink = (os.stat(src).st_dev ==
669 hardlink = (os.stat(src).st_dev ==
670 os.stat(os.path.dirname(dst)).st_dev)
670 os.stat(os.path.dirname(dst)).st_dev)
671
671
672 if os.path.isdir(src):
672 if os.path.isdir(src):
673 os.mkdir(dst)
673 os.mkdir(dst)
674 for name, kind in osutil.listdir(src):
674 for name, kind in osutil.listdir(src):
675 srcname = os.path.join(src, name)
675 srcname = os.path.join(src, name)
676 dstname = os.path.join(dst, name)
676 dstname = os.path.join(dst, name)
677 copyfiles(srcname, dstname, hardlink)
677 copyfiles(srcname, dstname, hardlink)
678 else:
678 else:
679 if hardlink:
679 if hardlink:
680 try:
680 try:
681 os_link(src, dst)
681 os_link(src, dst)
682 except (IOError, OSError):
682 except (IOError, OSError):
683 hardlink = False
683 hardlink = False
684 shutil.copy(src, dst)
684 shutil.copy(src, dst)
685 else:
685 else:
686 shutil.copy(src, dst)
686 shutil.copy(src, dst)
687
687
688 class path_auditor(object):
688 class path_auditor(object):
689 '''ensure that a filesystem path contains no banned components.
689 '''ensure that a filesystem path contains no banned components.
690 the following properties of a path are checked:
690 the following properties of a path are checked:
691
691
692 - under top-level .hg
692 - under top-level .hg
693 - starts at the root of a windows drive
693 - starts at the root of a windows drive
694 - contains ".."
694 - contains ".."
695 - traverses a symlink (e.g. a/symlink_here/b)
695 - traverses a symlink (e.g. a/symlink_here/b)
696 - inside a nested repository'''
696 - inside a nested repository'''
697
697
698 def __init__(self, root):
698 def __init__(self, root):
699 self.audited = set()
699 self.audited = set()
700 self.auditeddir = set()
700 self.auditeddir = set()
701 self.root = root
701 self.root = root
702
702
703 def __call__(self, path):
703 def __call__(self, path):
704 if path in self.audited:
704 if path in self.audited:
705 return
705 return
706 normpath = os.path.normcase(path)
706 normpath = os.path.normcase(path)
707 parts = normpath.split(os.sep)
707 parts = normpath.split(os.sep)
708 if (os.path.splitdrive(path)[0] or parts[0] in ('.hg', '')
708 if (os.path.splitdrive(path)[0] or parts[0] in ('.hg', '')
709 or os.pardir in parts):
709 or os.pardir in parts):
710 raise Abort(_("path contains illegal component: %s") % path)
710 raise Abort(_("path contains illegal component: %s") % path)
711 def check(prefix):
711 def check(prefix):
712 curpath = os.path.join(self.root, prefix)
712 curpath = os.path.join(self.root, prefix)
713 try:
713 try:
714 st = os.lstat(curpath)
714 st = os.lstat(curpath)
715 except OSError, err:
715 except OSError, err:
716 # EINVAL can be raised as invalid path syntax under win32.
716 # EINVAL can be raised as invalid path syntax under win32.
717 # They must be ignored for patterns can be checked too.
717 # They must be ignored for patterns can be checked too.
718 if err.errno not in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):
718 if err.errno not in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):
719 raise
719 raise
720 else:
720 else:
721 if stat.S_ISLNK(st.st_mode):
721 if stat.S_ISLNK(st.st_mode):
722 raise Abort(_('path %r traverses symbolic link %r') %
722 raise Abort(_('path %r traverses symbolic link %r') %
723 (path, prefix))
723 (path, prefix))
724 elif (stat.S_ISDIR(st.st_mode) and
724 elif (stat.S_ISDIR(st.st_mode) and
725 os.path.isdir(os.path.join(curpath, '.hg'))):
725 os.path.isdir(os.path.join(curpath, '.hg'))):
726 raise Abort(_('path %r is inside repo %r') %
726 raise Abort(_('path %r is inside repo %r') %
727 (path, prefix))
727 (path, prefix))
728
728
729 prefixes = []
729 prefixes = []
730 for c in strutil.rfindall(normpath, os.sep):
730 for c in strutil.rfindall(normpath, os.sep):
731 prefix = normpath[:c]
731 prefix = normpath[:c]
732 if prefix in self.auditeddir:
732 if prefix in self.auditeddir:
733 break
733 break
734 check(prefix)
734 check(prefix)
735 prefixes.append(prefix)
735 prefixes.append(prefix)
736
736
737 self.audited.add(path)
737 self.audited.add(path)
738 # only add prefixes to the cache after checking everything: we don't
738 # only add prefixes to the cache after checking everything: we don't
739 # want to add "foo/bar/baz" before checking if there's a "foo/.hg"
739 # want to add "foo/bar/baz" before checking if there's a "foo/.hg"
740 self.auditeddir.update(prefixes)
740 self.auditeddir.update(prefixes)
741
741
742 def _makelock_file(info, pathname):
742 def _makelock_file(info, pathname):
743 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
743 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
744 os.write(ld, info)
744 os.write(ld, info)
745 os.close(ld)
745 os.close(ld)
746
746
747 def _readlock_file(pathname):
747 def _readlock_file(pathname):
748 return posixfile(pathname).read()
748 return posixfile(pathname).read()
749
749
750 def nlinks(pathname):
750 def nlinks(pathname):
751 """Return number of hardlinks for the given file."""
751 """Return number of hardlinks for the given file."""
752 return os.lstat(pathname).st_nlink
752 return os.lstat(pathname).st_nlink
753
753
754 if hasattr(os, 'link'):
754 if hasattr(os, 'link'):
755 os_link = os.link
755 os_link = os.link
756 else:
756 else:
757 def os_link(src, dst):
757 def os_link(src, dst):
758 raise OSError(0, _("Hardlinks not supported"))
758 raise OSError(0, _("Hardlinks not supported"))
759
759
760 def fstat(fp):
760 def fstat(fp):
761 '''stat file object that may not have fileno method.'''
761 '''stat file object that may not have fileno method.'''
762 try:
762 try:
763 return os.fstat(fp.fileno())
763 return os.fstat(fp.fileno())
764 except AttributeError:
764 except AttributeError:
765 return os.stat(fp.name)
765 return os.stat(fp.name)
766
766
767 posixfile = file
767 posixfile = file
768
768
769 def is_win_9x():
769 def is_win_9x():
770 '''return true if run on windows 95, 98 or me.'''
770 '''return true if run on windows 95, 98 or me.'''
771 try:
771 try:
772 return sys.getwindowsversion()[3] == 1
772 return sys.getwindowsversion()[3] == 1
773 except AttributeError:
773 except AttributeError:
774 return os.name == 'nt' and 'command' in os.environ.get('comspec', '')
774 return os.name == 'nt' and 'command' in os.environ.get('comspec', '')
775
775
776 getuser_fallback = None
776 getuser_fallback = None
777
777
778 def getuser():
778 def getuser():
779 '''return name of current user'''
779 '''return name of current user'''
780 try:
780 try:
781 return getpass.getuser()
781 return getpass.getuser()
782 except ImportError:
782 except ImportError:
783 # import of pwd will fail on windows - try fallback
783 # import of pwd will fail on windows - try fallback
784 if getuser_fallback:
784 if getuser_fallback:
785 return getuser_fallback()
785 return getuser_fallback()
786 # raised if win32api not available
786 # raised if win32api not available
787 raise Abort(_('user name not available - set USERNAME '
787 raise Abort(_('user name not available - set USERNAME '
788 'environment variable'))
788 'environment variable'))
789
789
790 def username(uid=None):
790 def username(uid=None):
791 """Return the name of the user with the given uid.
791 """Return the name of the user with the given uid.
792
792
793 If uid is None, return the name of the current user."""
793 If uid is None, return the name of the current user."""
794 try:
794 try:
795 import pwd
795 import pwd
796 if uid is None:
796 if uid is None:
797 uid = os.getuid()
797 uid = os.getuid()
798 try:
798 try:
799 return pwd.getpwuid(uid)[0]
799 return pwd.getpwuid(uid)[0]
800 except KeyError:
800 except KeyError:
801 return str(uid)
801 return str(uid)
802 except ImportError:
802 except ImportError:
803 return None
803 return None
804
804
805 def groupname(gid=None):
805 def groupname(gid=None):
806 """Return the name of the group with the given gid.
806 """Return the name of the group with the given gid.
807
807
808 If gid is None, return the name of the current group."""
808 If gid is None, return the name of the current group."""
809 try:
809 try:
810 import grp
810 import grp
811 if gid is None:
811 if gid is None:
812 gid = os.getgid()
812 gid = os.getgid()
813 try:
813 try:
814 return grp.getgrgid(gid)[0]
814 return grp.getgrgid(gid)[0]
815 except KeyError:
815 except KeyError:
816 return str(gid)
816 return str(gid)
817 except ImportError:
817 except ImportError:
818 return None
818 return None
819
819
820 # File system features
820 # File system features
821
821
822 def checkfolding(path):
822 def checkfolding(path):
823 """
823 """
824 Check whether the given path is on a case-sensitive filesystem
824 Check whether the given path is on a case-sensitive filesystem
825
825
826 Requires a path (like /foo/.hg) ending with a foldable final
826 Requires a path (like /foo/.hg) ending with a foldable final
827 directory component.
827 directory component.
828 """
828 """
829 s1 = os.stat(path)
829 s1 = os.stat(path)
830 d, b = os.path.split(path)
830 d, b = os.path.split(path)
831 p2 = os.path.join(d, b.upper())
831 p2 = os.path.join(d, b.upper())
832 if path == p2:
832 if path == p2:
833 p2 = os.path.join(d, b.lower())
833 p2 = os.path.join(d, b.lower())
834 try:
834 try:
835 s2 = os.stat(p2)
835 s2 = os.stat(p2)
836 if s2 == s1:
836 if s2 == s1:
837 return False
837 return False
838 return True
838 return True
839 except:
839 except:
840 return True
840 return True
841
841
842 def checkexec(path):
842 def checkexec(path):
843 """
843 """
844 Check whether the given path is on a filesystem with UNIX-like exec flags
844 Check whether the given path is on a filesystem with UNIX-like exec flags
845
845
846 Requires a directory (like /foo/.hg)
846 Requires a directory (like /foo/.hg)
847 """
847 """
848 try:
848 try:
849 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
849 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
850 fh, fn = tempfile.mkstemp("", "", path)
850 fh, fn = tempfile.mkstemp("", "", path)
851 os.close(fh)
851 os.close(fh)
852 m = os.stat(fn).st_mode
852 m = os.stat(fn).st_mode
853 # VFAT on Linux can flip mode but it doesn't persist a FS remount.
853 # VFAT on Linux can flip mode but it doesn't persist a FS remount.
854 # frequently we can detect it if files are created with exec bit on.
854 # frequently we can detect it if files are created with exec bit on.
855 new_file_has_exec = m & EXECFLAGS
855 new_file_has_exec = m & EXECFLAGS
856 os.chmod(fn, m ^ EXECFLAGS)
856 os.chmod(fn, m ^ EXECFLAGS)
857 exec_flags_cannot_flip = (os.stat(fn).st_mode == m)
857 exec_flags_cannot_flip = (os.stat(fn).st_mode == m)
858 os.unlink(fn)
858 os.unlink(fn)
859 except (IOError,OSError):
859 except (IOError,OSError):
860 # we don't care, the user probably won't be able to commit anyway
860 # we don't care, the user probably won't be able to commit anyway
861 return False
861 return False
862 return not (new_file_has_exec or exec_flags_cannot_flip)
862 return not (new_file_has_exec or exec_flags_cannot_flip)
863
863
864 def execfunc(path, fallback):
864 def execfunc(path, fallback):
865 '''return an is_exec() function with default to fallback'''
865 '''return an is_exec() function with default to fallback'''
866 if checkexec(path):
866 if checkexec(path):
867 return lambda x: is_exec(os.path.join(path, x))
867 return lambda x: is_exec(os.path.join(path, x))
868 return fallback
868 return fallback
869
869
870 def checklink(path):
870 def checklink(path):
871 """check whether the given path is on a symlink-capable filesystem"""
871 """check whether the given path is on a symlink-capable filesystem"""
872 # mktemp is not racy because symlink creation will fail if the
872 # mktemp is not racy because symlink creation will fail if the
873 # file already exists
873 # file already exists
874 name = tempfile.mktemp(dir=path)
874 name = tempfile.mktemp(dir=path)
875 try:
875 try:
876 os.symlink(".", name)
876 os.symlink(".", name)
877 os.unlink(name)
877 os.unlink(name)
878 return True
878 return True
879 except (OSError, AttributeError):
879 except (OSError, AttributeError):
880 return False
880 return False
881
881
882 def linkfunc(path, fallback):
882 def linkfunc(path, fallback):
883 '''return an is_link() function with default to fallback'''
883 '''return an is_link() function with default to fallback'''
884 if checklink(path):
884 if checklink(path):
885 return lambda x: os.path.islink(os.path.join(path, x))
885 return lambda x: os.path.islink(os.path.join(path, x))
886 return fallback
886 return fallback
887
887
888 _umask = os.umask(0)
888 _umask = os.umask(0)
889 os.umask(_umask)
889 os.umask(_umask)
890
890
891 def needbinarypatch():
891 def needbinarypatch():
892 """return True if patches should be applied in binary mode by default."""
892 """return True if patches should be applied in binary mode by default."""
893 return os.name == 'nt'
893 return os.name == 'nt'
894
894
895 # Platform specific variants
895 # Platform specific variants
896 if os.name == 'nt':
896 if os.name == 'nt':
897 import msvcrt
897 import msvcrt
898 nulldev = 'NUL:'
898 nulldev = 'NUL:'
899
899
900 class winstdout:
900 class winstdout:
901 '''stdout on windows misbehaves if sent through a pipe'''
901 '''stdout on windows misbehaves if sent through a pipe'''
902
902
903 def __init__(self, fp):
903 def __init__(self, fp):
904 self.fp = fp
904 self.fp = fp
905
905
906 def __getattr__(self, key):
906 def __getattr__(self, key):
907 return getattr(self.fp, key)
907 return getattr(self.fp, key)
908
908
909 def close(self):
909 def close(self):
910 try:
910 try:
911 self.fp.close()
911 self.fp.close()
912 except: pass
912 except: pass
913
913
914 def write(self, s):
914 def write(self, s):
915 try:
915 try:
916 return self.fp.write(s)
916 return self.fp.write(s)
917 except IOError, inst:
917 except IOError, inst:
918 if inst.errno != 0: raise
918 if inst.errno != 0: raise
919 self.close()
919 self.close()
920 raise IOError(errno.EPIPE, 'Broken pipe')
920 raise IOError(errno.EPIPE, 'Broken pipe')
921
921
922 def flush(self):
922 def flush(self):
923 try:
923 try:
924 return self.fp.flush()
924 return self.fp.flush()
925 except IOError, inst:
925 except IOError, inst:
926 if inst.errno != errno.EINVAL: raise
926 if inst.errno != errno.EINVAL: raise
927 self.close()
927 self.close()
928 raise IOError(errno.EPIPE, 'Broken pipe')
928 raise IOError(errno.EPIPE, 'Broken pipe')
929
929
930 sys.stdout = winstdout(sys.stdout)
930 sys.stdout = winstdout(sys.stdout)
931
931
932 def system_rcpath():
932 def system_rcpath():
933 try:
933 try:
934 return system_rcpath_win32()
934 return system_rcpath_win32()
935 except:
935 except:
936 return [r'c:\mercurial\mercurial.ini']
936 return [r'c:\mercurial\mercurial.ini']
937
937
938 def user_rcpath():
938 def user_rcpath():
939 '''return os-specific hgrc search path to the user dir'''
939 '''return os-specific hgrc search path to the user dir'''
940 try:
940 try:
941 userrc = user_rcpath_win32()
941 userrc = user_rcpath_win32()
942 except:
942 except:
943 userrc = os.path.join(os.path.expanduser('~'), 'mercurial.ini')
943 userrc = os.path.join(os.path.expanduser('~'), 'mercurial.ini')
944 path = [userrc]
944 path = [userrc]
945 userprofile = os.environ.get('USERPROFILE')
945 userprofile = os.environ.get('USERPROFILE')
946 if userprofile:
946 if userprofile:
947 path.append(os.path.join(userprofile, 'mercurial.ini'))
947 path.append(os.path.join(userprofile, 'mercurial.ini'))
948 return path
948 return path
949
949
950 def parse_patch_output(output_line):
950 def parse_patch_output(output_line):
951 """parses the output produced by patch and returns the file name"""
951 """parses the output produced by patch and returns the file name"""
952 pf = output_line[14:]
952 pf = output_line[14:]
953 if pf[0] == '`':
953 if pf[0] == '`':
954 pf = pf[1:-1] # Remove the quotes
954 pf = pf[1:-1] # Remove the quotes
955 return pf
955 return pf
956
956
957 def testpid(pid):
957 def testpid(pid):
958 '''return False if pid dead, True if running or not known'''
958 '''return False if pid dead, True if running or not known'''
959 return True
959 return True
960
960
961 def set_exec(f, mode):
961 def set_exec(f, mode):
962 pass
962 pass
963
963
964 def set_link(f, mode):
964 def set_link(f, mode):
965 pass
965 pass
966
966
967 def set_binary(fd):
967 def set_binary(fd):
968 msvcrt.setmode(fd.fileno(), os.O_BINARY)
968 msvcrt.setmode(fd.fileno(), os.O_BINARY)
969
969
970 def pconvert(path):
970 def pconvert(path):
971 return path.replace("\\", "/")
971 return path.replace("\\", "/")
972
972
973 def localpath(path):
973 def localpath(path):
974 return path.replace('/', '\\')
974 return path.replace('/', '\\')
975
975
976 def normpath(path):
976 def normpath(path):
977 return pconvert(os.path.normpath(path))
977 return pconvert(os.path.normpath(path))
978
978
979 makelock = _makelock_file
979 makelock = _makelock_file
980 readlock = _readlock_file
980 readlock = _readlock_file
981
981
982 def samestat(s1, s2):
982 def samestat(s1, s2):
983 return False
983 return False
984
984
985 # A sequence of backslashes is special iff it precedes a double quote:
985 # A sequence of backslashes is special iff it precedes a double quote:
986 # - if there's an even number of backslashes, the double quote is not
986 # - if there's an even number of backslashes, the double quote is not
987 # quoted (i.e. it ends the quoted region)
987 # quoted (i.e. it ends the quoted region)
988 # - if there's an odd number of backslashes, the double quote is quoted
988 # - if there's an odd number of backslashes, the double quote is quoted
989 # - in both cases, every pair of backslashes is unquoted into a single
989 # - in both cases, every pair of backslashes is unquoted into a single
990 # backslash
990 # backslash
991 # (See http://msdn2.microsoft.com/en-us/library/a1y7w461.aspx )
991 # (See http://msdn2.microsoft.com/en-us/library/a1y7w461.aspx )
992 # So, to quote a string, we must surround it in double quotes, double
992 # So, to quote a string, we must surround it in double quotes, double
993 # the number of backslashes that preceed double quotes and add another
993 # the number of backslashes that preceed double quotes and add another
994 # backslash before every double quote (being careful with the double
994 # backslash before every double quote (being careful with the double
995 # quote we've appended to the end)
995 # quote we've appended to the end)
996 _quotere = None
996 _quotere = None
997 def shellquote(s):
997 def shellquote(s):
998 global _quotere
998 global _quotere
999 if _quotere is None:
999 if _quotere is None:
1000 _quotere = re.compile(r'(\\*)("|\\$)')
1000 _quotere = re.compile(r'(\\*)("|\\$)')
1001 return '"%s"' % _quotere.sub(r'\1\1\\\2', s)
1001 return '"%s"' % _quotere.sub(r'\1\1\\\2', s)
1002
1002
1003 def quotecommand(cmd):
1003 def quotecommand(cmd):
1004 """Build a command string suitable for os.popen* calls."""
1004 """Build a command string suitable for os.popen* calls."""
1005 # The extra quotes are needed because popen* runs the command
1005 # The extra quotes are needed because popen* runs the command
1006 # through the current COMSPEC. cmd.exe suppress enclosing quotes.
1006 # through the current COMSPEC. cmd.exe suppress enclosing quotes.
1007 return '"' + cmd + '"'
1007 return '"' + cmd + '"'
1008
1008
1009 def popen(command):
1009 def popen(command):
1010 # Work around "popen spawned process may not write to stdout
1010 # Work around "popen spawned process may not write to stdout
1011 # under windows"
1011 # under windows"
1012 # http://bugs.python.org/issue1366
1012 # http://bugs.python.org/issue1366
1013 command += " 2> %s" % nulldev
1013 command += " 2> %s" % nulldev
1014 return os.popen(quotecommand(command))
1014 return os.popen(quotecommand(command))
1015
1015
1016 def explain_exit(code):
1016 def explain_exit(code):
1017 return _("exited with status %d") % code, code
1017 return _("exited with status %d") % code, code
1018
1018
1019 # if you change this stub into a real check, please try to implement the
1019 # if you change this stub into a real check, please try to implement the
1020 # username and groupname functions above, too.
1020 # username and groupname functions above, too.
1021 def isowner(fp, st=None):
1021 def isowner(fp, st=None):
1022 return True
1022 return True
1023
1023
1024 def find_in_path(name, path, default=None):
1024 def find_in_path(name, path, default=None):
1025 '''find name in search path. path can be string (will be split
1025 '''find name in search path. path can be string (will be split
1026 with os.pathsep), or iterable thing that returns strings. if name
1026 with os.pathsep), or iterable thing that returns strings. if name
1027 found, return path to name. else return default. name is looked up
1027 found, return path to name. else return default. name is looked up
1028 using cmd.exe rules, using PATHEXT.'''
1028 using cmd.exe rules, using PATHEXT.'''
1029 if isinstance(path, str):
1029 if isinstance(path, str):
1030 path = path.split(os.pathsep)
1030 path = path.split(os.pathsep)
1031
1031
1032 pathext = os.environ.get('PATHEXT', '.COM;.EXE;.BAT;.CMD')
1032 pathext = os.environ.get('PATHEXT', '.COM;.EXE;.BAT;.CMD')
1033 pathext = pathext.lower().split(os.pathsep)
1033 pathext = pathext.lower().split(os.pathsep)
1034 isexec = os.path.splitext(name)[1].lower() in pathext
1034 isexec = os.path.splitext(name)[1].lower() in pathext
1035
1035
1036 for p in path:
1036 for p in path:
1037 p_name = os.path.join(p, name)
1037 p_name = os.path.join(p, name)
1038
1038
1039 if isexec and os.path.exists(p_name):
1039 if isexec and os.path.exists(p_name):
1040 return p_name
1040 return p_name
1041
1041
1042 for ext in pathext:
1042 for ext in pathext:
1043 p_name_ext = p_name + ext
1043 p_name_ext = p_name + ext
1044 if os.path.exists(p_name_ext):
1044 if os.path.exists(p_name_ext):
1045 return p_name_ext
1045 return p_name_ext
1046 return default
1046 return default
1047
1047
1048 def set_signal_handler():
1048 def set_signal_handler():
1049 try:
1049 try:
1050 set_signal_handler_win32()
1050 set_signal_handler_win32()
1051 except NameError:
1051 except NameError:
1052 pass
1052 pass
1053
1053
1054 try:
1054 try:
1055 # override functions with win32 versions if possible
1055 # override functions with win32 versions if possible
1056 from util_win32 import *
1056 from util_win32 import *
1057 if not is_win_9x():
1057 if not is_win_9x():
1058 posixfile = posixfile_nt
1058 posixfile = posixfile_nt
1059 except ImportError:
1059 except ImportError:
1060 pass
1060 pass
1061
1061
1062 else:
1062 else:
1063 nulldev = '/dev/null'
1063 nulldev = '/dev/null'
1064
1064
1065 def rcfiles(path):
1065 def rcfiles(path):
1066 rcs = [os.path.join(path, 'hgrc')]
1066 rcs = [os.path.join(path, 'hgrc')]
1067 rcdir = os.path.join(path, 'hgrc.d')
1067 rcdir = os.path.join(path, 'hgrc.d')
1068 try:
1068 try:
1069 rcs.extend([os.path.join(rcdir, f)
1069 rcs.extend([os.path.join(rcdir, f)
1070 for f, kind in osutil.listdir(rcdir)
1070 for f, kind in osutil.listdir(rcdir)
1071 if f.endswith(".rc")])
1071 if f.endswith(".rc")])
1072 except OSError:
1072 except OSError:
1073 pass
1073 pass
1074 return rcs
1074 return rcs
1075
1075
1076 def system_rcpath():
1076 def system_rcpath():
1077 path = []
1077 path = []
1078 # old mod_python does not set sys.argv
1078 # old mod_python does not set sys.argv
1079 if len(getattr(sys, 'argv', [])) > 0:
1079 if len(getattr(sys, 'argv', [])) > 0:
1080 path.extend(rcfiles(os.path.dirname(sys.argv[0]) +
1080 path.extend(rcfiles(os.path.dirname(sys.argv[0]) +
1081 '/../etc/mercurial'))
1081 '/../etc/mercurial'))
1082 path.extend(rcfiles('/etc/mercurial'))
1082 path.extend(rcfiles('/etc/mercurial'))
1083 return path
1083 return path
1084
1084
1085 def user_rcpath():
1085 def user_rcpath():
1086 return [os.path.expanduser('~/.hgrc')]
1086 return [os.path.expanduser('~/.hgrc')]
1087
1087
1088 def parse_patch_output(output_line):
1088 def parse_patch_output(output_line):
1089 """parses the output produced by patch and returns the file name"""
1089 """parses the output produced by patch and returns the file name"""
1090 pf = output_line[14:]
1090 pf = output_line[14:]
1091 if os.sys.platform == 'OpenVMS':
1091 if os.sys.platform == 'OpenVMS':
1092 if pf[0] == '`':
1092 if pf[0] == '`':
1093 pf = pf[1:-1] # Remove the quotes
1093 pf = pf[1:-1] # Remove the quotes
1094 else:
1094 else:
1095 if pf.startswith("'") and pf.endswith("'") and " " in pf:
1095 if pf.startswith("'") and pf.endswith("'") and " " in pf:
1096 pf = pf[1:-1] # Remove the quotes
1096 pf = pf[1:-1] # Remove the quotes
1097 return pf
1097 return pf
1098
1098
1099 def is_exec(f):
1099 def is_exec(f):
1100 """check whether a file is executable"""
1100 """check whether a file is executable"""
1101 return (os.lstat(f).st_mode & 0100 != 0)
1101 return (os.lstat(f).st_mode & 0100 != 0)
1102
1102
1103 def force_chmod(f, s):
1103 def force_chmod(f, s):
1104 try:
1104 try:
1105 os.chmod(f, s)
1105 os.chmod(f, s)
1106 except OSError, inst:
1106 except OSError, inst:
1107 if inst.errno != errno.EPERM:
1107 if inst.errno != errno.EPERM:
1108 raise
1108 raise
1109 # maybe we don't own the file, try copying it
1109 # maybe we don't own the file, try copying it
1110 new_f = mktempcopy(f)
1110 new_f = mktempcopy(f)
1111 os.chmod(new_f, s)
1111 os.chmod(new_f, s)
1112 os.rename(new_f, f)
1112 os.rename(new_f, f)
1113
1113
1114 def set_exec(f, mode):
1114 def set_exec(f, mode):
1115 s = os.lstat(f).st_mode
1115 s = os.lstat(f).st_mode
1116 if stat.S_ISLNK(s) or (s & 0100 != 0) == mode:
1116 if stat.S_ISLNK(s) or (s & 0100 != 0) == mode:
1117 return
1117 return
1118 if mode:
1118 if mode:
1119 # Turn on +x for every +r bit when making a file executable
1119 # Turn on +x for every +r bit when making a file executable
1120 # and obey umask.
1120 # and obey umask.
1121 force_chmod(f, s | (s & 0444) >> 2 & ~_umask)
1121 force_chmod(f, s | (s & 0444) >> 2 & ~_umask)
1122 else:
1122 else:
1123 force_chmod(f, s & 0666)
1123 force_chmod(f, s & 0666)
1124
1124
1125 def set_link(f, mode):
1125 def set_link(f, mode):
1126 """make a file a symbolic link/regular file
1126 """make a file a symbolic link/regular file
1127
1127
1128 if a file is changed to a link, its contents become the link data
1128 if a file is changed to a link, its contents become the link data
1129 if a link is changed to a file, its link data become its contents
1129 if a link is changed to a file, its link data become its contents
1130 """
1130 """
1131
1131
1132 m = os.path.islink(f)
1132 m = os.path.islink(f)
1133 if m == bool(mode):
1133 if m == bool(mode):
1134 return
1134 return
1135
1135
1136 if mode: # switch file to link
1136 if mode: # switch file to link
1137 data = file(f).read()
1137 data = file(f).read()
1138 os.unlink(f)
1138 os.unlink(f)
1139 os.symlink(data, f)
1139 os.symlink(data, f)
1140 else:
1140 else:
1141 data = os.readlink(f)
1141 data = os.readlink(f)
1142 os.unlink(f)
1142 os.unlink(f)
1143 file(f, "w").write(data)
1143 file(f, "w").write(data)
1144
1144
1145 def set_binary(fd):
1145 def set_binary(fd):
1146 pass
1146 pass
1147
1147
1148 def pconvert(path):
1148 def pconvert(path):
1149 return path
1149 return path
1150
1150
1151 def localpath(path):
1151 def localpath(path):
1152 return path
1152 return path
1153
1153
1154 normpath = os.path.normpath
1154 normpath = os.path.normpath
1155 samestat = os.path.samestat
1155 samestat = os.path.samestat
1156
1156
1157 def makelock(info, pathname):
1157 def makelock(info, pathname):
1158 try:
1158 try:
1159 os.symlink(info, pathname)
1159 os.symlink(info, pathname)
1160 except OSError, why:
1160 except OSError, why:
1161 if why.errno == errno.EEXIST:
1161 if why.errno == errno.EEXIST:
1162 raise
1162 raise
1163 else:
1163 else:
1164 _makelock_file(info, pathname)
1164 _makelock_file(info, pathname)
1165
1165
1166 def readlock(pathname):
1166 def readlock(pathname):
1167 try:
1167 try:
1168 return os.readlink(pathname)
1168 return os.readlink(pathname)
1169 except OSError, why:
1169 except OSError, why:
1170 if why.errno in (errno.EINVAL, errno.ENOSYS):
1170 if why.errno in (errno.EINVAL, errno.ENOSYS):
1171 return _readlock_file(pathname)
1171 return _readlock_file(pathname)
1172 else:
1172 else:
1173 raise
1173 raise
1174
1174
1175 def shellquote(s):
1175 def shellquote(s):
1176 if os.sys.platform == 'OpenVMS':
1176 if os.sys.platform == 'OpenVMS':
1177 return '"%s"' % s
1177 return '"%s"' % s
1178 else:
1178 else:
1179 return "'%s'" % s.replace("'", "'\\''")
1179 return "'%s'" % s.replace("'", "'\\''")
1180
1180
1181 def quotecommand(cmd):
1181 def quotecommand(cmd):
1182 return cmd
1182 return cmd
1183
1183
1184 def popen(command):
1184 def popen(command):
1185 return os.popen(command)
1185 return os.popen(command)
1186
1186
1187 def testpid(pid):
1187 def testpid(pid):
1188 '''return False if pid dead, True if running or not sure'''
1188 '''return False if pid dead, True if running or not sure'''
1189 if os.sys.platform == 'OpenVMS':
1189 if os.sys.platform == 'OpenVMS':
1190 return True
1190 return True
1191 try:
1191 try:
1192 os.kill(pid, 0)
1192 os.kill(pid, 0)
1193 return True
1193 return True
1194 except OSError, inst:
1194 except OSError, inst:
1195 return inst.errno != errno.ESRCH
1195 return inst.errno != errno.ESRCH
1196
1196
1197 def explain_exit(code):
1197 def explain_exit(code):
1198 """return a 2-tuple (desc, code) describing a process's status"""
1198 """return a 2-tuple (desc, code) describing a process's status"""
1199 if os.WIFEXITED(code):
1199 if os.WIFEXITED(code):
1200 val = os.WEXITSTATUS(code)
1200 val = os.WEXITSTATUS(code)
1201 return _("exited with status %d") % val, val
1201 return _("exited with status %d") % val, val
1202 elif os.WIFSIGNALED(code):
1202 elif os.WIFSIGNALED(code):
1203 val = os.WTERMSIG(code)
1203 val = os.WTERMSIG(code)
1204 return _("killed by signal %d") % val, val
1204 return _("killed by signal %d") % val, val
1205 elif os.WIFSTOPPED(code):
1205 elif os.WIFSTOPPED(code):
1206 val = os.WSTOPSIG(code)
1206 val = os.WSTOPSIG(code)
1207 return _("stopped by signal %d") % val, val
1207 return _("stopped by signal %d") % val, val
1208 raise ValueError(_("invalid exit code"))
1208 raise ValueError(_("invalid exit code"))
1209
1209
1210 def isowner(fp, st=None):
1210 def isowner(fp, st=None):
1211 """Return True if the file object f belongs to the current user.
1211 """Return True if the file object f belongs to the current user.
1212
1212
1213 The return value of a util.fstat(f) may be passed as the st argument.
1213 The return value of a util.fstat(f) may be passed as the st argument.
1214 """
1214 """
1215 if st is None:
1215 if st is None:
1216 st = fstat(fp)
1216 st = fstat(fp)
1217 return st.st_uid == os.getuid()
1217 return st.st_uid == os.getuid()
1218
1218
1219 def find_in_path(name, path, default=None):
1219 def find_in_path(name, path, default=None):
1220 '''find name in search path. path can be string (will be split
1220 '''find name in search path. path can be string (will be split
1221 with os.pathsep), or iterable thing that returns strings. if name
1221 with os.pathsep), or iterable thing that returns strings. if name
1222 found, return path to name. else return default.'''
1222 found, return path to name. else return default.'''
1223 if isinstance(path, str):
1223 if isinstance(path, str):
1224 path = path.split(os.pathsep)
1224 path = path.split(os.pathsep)
1225 for p in path:
1225 for p in path:
1226 p_name = os.path.join(p, name)
1226 p_name = os.path.join(p, name)
1227 if os.path.exists(p_name):
1227 if os.path.exists(p_name):
1228 return p_name
1228 return p_name
1229 return default
1229 return default
1230
1230
1231 def set_signal_handler():
1231 def set_signal_handler():
1232 pass
1232 pass
1233
1233
1234 def find_exe(name, default=None):
1234 def find_exe(name, default=None):
1235 '''find path of an executable.
1235 '''find path of an executable.
1236 if name contains a path component, return it as is. otherwise,
1236 if name contains a path component, return it as is. otherwise,
1237 use normal executable search path.'''
1237 use normal executable search path.'''
1238
1238
1239 if os.sep in name or sys.platform == 'OpenVMS':
1239 if os.sep in name or sys.platform == 'OpenVMS':
1240 # don't check the executable bit. if the file isn't
1240 # don't check the executable bit. if the file isn't
1241 # executable, whoever tries to actually run it will give a
1241 # executable, whoever tries to actually run it will give a
1242 # much more useful error message.
1242 # much more useful error message.
1243 return name
1243 return name
1244 return find_in_path(name, os.environ.get('PATH', ''), default=default)
1244 return find_in_path(name, os.environ.get('PATH', ''), default=default)
1245
1245
1246 def _buildencodefun():
1246 def _buildencodefun():
1247 e = '_'
1247 e = '_'
1248 win_reserved = [ord(x) for x in '\\:*?"<>|']
1248 win_reserved = [ord(x) for x in '\\:*?"<>|']
1249 cmap = dict([ (chr(x), chr(x)) for x in xrange(127) ])
1249 cmap = dict([ (chr(x), chr(x)) for x in xrange(127) ])
1250 for x in (range(32) + range(126, 256) + win_reserved):
1250 for x in (range(32) + range(126, 256) + win_reserved):
1251 cmap[chr(x)] = "~%02x" % x
1251 cmap[chr(x)] = "~%02x" % x
1252 for x in range(ord("A"), ord("Z")+1) + [ord(e)]:
1252 for x in range(ord("A"), ord("Z")+1) + [ord(e)]:
1253 cmap[chr(x)] = e + chr(x).lower()
1253 cmap[chr(x)] = e + chr(x).lower()
1254 dmap = {}
1254 dmap = {}
1255 for k, v in cmap.iteritems():
1255 for k, v in cmap.iteritems():
1256 dmap[v] = k
1256 dmap[v] = k
1257 def decode(s):
1257 def decode(s):
1258 i = 0
1258 i = 0
1259 while i < len(s):
1259 while i < len(s):
1260 for l in xrange(1, 4):
1260 for l in xrange(1, 4):
1261 try:
1261 try:
1262 yield dmap[s[i:i+l]]
1262 yield dmap[s[i:i+l]]
1263 i += l
1263 i += l
1264 break
1264 break
1265 except KeyError:
1265 except KeyError:
1266 pass
1266 pass
1267 else:
1267 else:
1268 raise KeyError
1268 raise KeyError
1269 return (lambda s: "".join([cmap[c] for c in s]),
1269 return (lambda s: "".join([cmap[c] for c in s]),
1270 lambda s: "".join(list(decode(s))))
1270 lambda s: "".join(list(decode(s))))
1271
1271
1272 encodefilename, decodefilename = _buildencodefun()
1272 encodefilename, decodefilename = _buildencodefun()
1273
1273
1274 def encodedopener(openerfn, fn):
1274 def encodedopener(openerfn, fn):
1275 def o(path, *args, **kw):
1275 def o(path, *args, **kw):
1276 return openerfn(fn(path), *args, **kw)
1276 return openerfn(fn(path), *args, **kw)
1277 return o
1277 return o
1278
1278
1279 def mktempcopy(name, emptyok=False):
1279 def mktempcopy(name, emptyok=False):
1280 """Create a temporary file with the same contents from name
1280 """Create a temporary file with the same contents from name
1281
1281
1282 The permission bits are copied from the original file.
1282 The permission bits are copied from the original file.
1283
1283
1284 If the temporary file is going to be truncated immediately, you
1284 If the temporary file is going to be truncated immediately, you
1285 can use emptyok=True as an optimization.
1285 can use emptyok=True as an optimization.
1286
1286
1287 Returns the name of the temporary file.
1287 Returns the name of the temporary file.
1288 """
1288 """
1289 d, fn = os.path.split(name)
1289 d, fn = os.path.split(name)
1290 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
1290 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
1291 os.close(fd)
1291 os.close(fd)
1292 # Temporary files are created with mode 0600, which is usually not
1292 # Temporary files are created with mode 0600, which is usually not
1293 # what we want. If the original file already exists, just copy
1293 # what we want. If the original file already exists, just copy
1294 # its mode. Otherwise, manually obey umask.
1294 # its mode. Otherwise, manually obey umask.
1295 try:
1295 try:
1296 st_mode = os.lstat(name).st_mode
1296 st_mode = os.lstat(name).st_mode
1297 except OSError, inst:
1297 except OSError, inst:
1298 if inst.errno != errno.ENOENT:
1298 if inst.errno != errno.ENOENT:
1299 raise
1299 raise
1300 st_mode = 0666 & ~_umask
1300 st_mode = 0666 & ~_umask
1301 os.chmod(temp, st_mode)
1301 os.chmod(temp, st_mode)
1302 if emptyok:
1302 if emptyok:
1303 return temp
1303 return temp
1304 try:
1304 try:
1305 try:
1305 try:
1306 ifp = posixfile(name, "rb")
1306 ifp = posixfile(name, "rb")
1307 except IOError, inst:
1307 except IOError, inst:
1308 if inst.errno == errno.ENOENT:
1308 if inst.errno == errno.ENOENT:
1309 return temp
1309 return temp
1310 if not getattr(inst, 'filename', None):
1310 if not getattr(inst, 'filename', None):
1311 inst.filename = name
1311 inst.filename = name
1312 raise
1312 raise
1313 ofp = posixfile(temp, "wb")
1313 ofp = posixfile(temp, "wb")
1314 for chunk in filechunkiter(ifp):
1314 for chunk in filechunkiter(ifp):
1315 ofp.write(chunk)
1315 ofp.write(chunk)
1316 ifp.close()
1316 ifp.close()
1317 ofp.close()
1317 ofp.close()
1318 except:
1318 except:
1319 try: os.unlink(temp)
1319 try: os.unlink(temp)
1320 except: pass
1320 except: pass
1321 raise
1321 raise
1322 return temp
1322 return temp
1323
1323
1324 class atomictempfile(posixfile):
1324 class atomictempfile(posixfile):
1325 """file-like object that atomically updates a file
1325 """file-like object that atomically updates a file
1326
1326
1327 All writes will be redirected to a temporary copy of the original
1327 All writes will be redirected to a temporary copy of the original
1328 file. When rename is called, the copy is renamed to the original
1328 file. When rename is called, the copy is renamed to the original
1329 name, making the changes visible.
1329 name, making the changes visible.
1330 """
1330 """
1331 def __init__(self, name, mode):
1331 def __init__(self, name, mode):
1332 self.__name = name
1332 self.__name = name
1333 self.temp = mktempcopy(name, emptyok=('w' in mode))
1333 self.temp = mktempcopy(name, emptyok=('w' in mode))
1334 posixfile.__init__(self, self.temp, mode)
1334 posixfile.__init__(self, self.temp, mode)
1335
1335
1336 def rename(self):
1336 def rename(self):
1337 if not self.closed:
1337 if not self.closed:
1338 posixfile.close(self)
1338 posixfile.close(self)
1339 rename(self.temp, localpath(self.__name))
1339 rename(self.temp, localpath(self.__name))
1340
1340
1341 def __del__(self):
1341 def __del__(self):
1342 if not self.closed:
1342 if not self.closed:
1343 try:
1343 try:
1344 os.unlink(self.temp)
1344 os.unlink(self.temp)
1345 except: pass
1345 except: pass
1346 posixfile.close(self)
1346 posixfile.close(self)
1347
1347
1348 class opener(object):
1348 class opener(object):
1349 """Open files relative to a base directory
1349 """Open files relative to a base directory
1350
1350
1351 This class is used to hide the details of COW semantics and
1351 This class is used to hide the details of COW semantics and
1352 remote file access from higher level code.
1352 remote file access from higher level code.
1353 """
1353 """
1354 def __init__(self, base, audit=True):
1354 def __init__(self, base, audit=True):
1355 self.base = base
1355 self.base = base
1356 if audit:
1356 if audit:
1357 self.audit_path = path_auditor(base)
1357 self.audit_path = path_auditor(base)
1358 else:
1358 else:
1359 self.audit_path = always
1359 self.audit_path = always
1360
1360
1361 def __getattr__(self, name):
1361 def __getattr__(self, name):
1362 if name == '_can_symlink':
1362 if name == '_can_symlink':
1363 self._can_symlink = checklink(self.base)
1363 self._can_symlink = checklink(self.base)
1364 return self._can_symlink
1364 return self._can_symlink
1365 raise AttributeError(name)
1365 raise AttributeError(name)
1366
1366
1367 def __call__(self, path, mode="r", text=False, atomictemp=False):
1367 def __call__(self, path, mode="r", text=False, atomictemp=False):
1368 self.audit_path(path)
1368 self.audit_path(path)
1369 f = os.path.join(self.base, path)
1369 f = os.path.join(self.base, path)
1370
1370
1371 if not text and "b" not in mode:
1371 if not text and "b" not in mode:
1372 mode += "b" # for that other OS
1372 mode += "b" # for that other OS
1373
1373
1374 if mode[0] != "r":
1374 if mode[0] != "r":
1375 try:
1375 try:
1376 nlink = nlinks(f)
1376 nlink = nlinks(f)
1377 except OSError:
1377 except OSError:
1378 nlink = 0
1378 nlink = 0
1379 d = os.path.dirname(f)
1379 d = os.path.dirname(f)
1380 if not os.path.isdir(d):
1380 if not os.path.isdir(d):
1381 os.makedirs(d)
1381 os.makedirs(d)
1382 if atomictemp:
1382 if atomictemp:
1383 return atomictempfile(f, mode)
1383 return atomictempfile(f, mode)
1384 if nlink > 1:
1384 if nlink > 1:
1385 rename(mktempcopy(f), f)
1385 rename(mktempcopy(f), f)
1386 return posixfile(f, mode)
1386 return posixfile(f, mode)
1387
1387
1388 def symlink(self, src, dst):
1388 def symlink(self, src, dst):
1389 self.audit_path(dst)
1389 self.audit_path(dst)
1390 linkname = os.path.join(self.base, dst)
1390 linkname = os.path.join(self.base, dst)
1391 try:
1391 try:
1392 os.unlink(linkname)
1392 os.unlink(linkname)
1393 except OSError:
1393 except OSError:
1394 pass
1394 pass
1395
1395
1396 dirname = os.path.dirname(linkname)
1396 dirname = os.path.dirname(linkname)
1397 if not os.path.exists(dirname):
1397 if not os.path.exists(dirname):
1398 os.makedirs(dirname)
1398 os.makedirs(dirname)
1399
1399
1400 if self._can_symlink:
1400 if self._can_symlink:
1401 try:
1401 try:
1402 os.symlink(src, linkname)
1402 os.symlink(src, linkname)
1403 except OSError, err:
1403 except OSError, err:
1404 raise OSError(err.errno, _('could not symlink to %r: %s') %
1404 raise OSError(err.errno, _('could not symlink to %r: %s') %
1405 (src, err.strerror), linkname)
1405 (src, err.strerror), linkname)
1406 else:
1406 else:
1407 f = self(dst, "w")
1407 f = self(dst, "w")
1408 f.write(src)
1408 f.write(src)
1409 f.close()
1409 f.close()
1410
1410
1411 class chunkbuffer(object):
1411 class chunkbuffer(object):
1412 """Allow arbitrary sized chunks of data to be efficiently read from an
1412 """Allow arbitrary sized chunks of data to be efficiently read from an
1413 iterator over chunks of arbitrary size."""
1413 iterator over chunks of arbitrary size."""
1414
1414
1415 def __init__(self, in_iter):
1415 def __init__(self, in_iter):
1416 """in_iter is the iterator that's iterating over the input chunks.
1416 """in_iter is the iterator that's iterating over the input chunks.
1417 targetsize is how big a buffer to try to maintain."""
1417 targetsize is how big a buffer to try to maintain."""
1418 self.iter = iter(in_iter)
1418 self.iter = iter(in_iter)
1419 self.buf = ''
1419 self.buf = ''
1420 self.targetsize = 2**16
1420 self.targetsize = 2**16
1421
1421
1422 def read(self, l):
1422 def read(self, l):
1423 """Read L bytes of data from the iterator of chunks of data.
1423 """Read L bytes of data from the iterator of chunks of data.
1424 Returns less than L bytes if the iterator runs dry."""
1424 Returns less than L bytes if the iterator runs dry."""
1425 if l > len(self.buf) and self.iter:
1425 if l > len(self.buf) and self.iter:
1426 # Clamp to a multiple of self.targetsize
1426 # Clamp to a multiple of self.targetsize
1427 targetsize = max(l, self.targetsize)
1427 targetsize = max(l, self.targetsize)
1428 collector = cStringIO.StringIO()
1428 collector = cStringIO.StringIO()
1429 collector.write(self.buf)
1429 collector.write(self.buf)
1430 collected = len(self.buf)
1430 collected = len(self.buf)
1431 for chunk in self.iter:
1431 for chunk in self.iter:
1432 collector.write(chunk)
1432 collector.write(chunk)
1433 collected += len(chunk)
1433 collected += len(chunk)
1434 if collected >= targetsize:
1434 if collected >= targetsize:
1435 break
1435 break
1436 if collected < targetsize:
1436 if collected < targetsize:
1437 self.iter = False
1437 self.iter = False
1438 self.buf = collector.getvalue()
1438 self.buf = collector.getvalue()
1439 if len(self.buf) == l:
1439 if len(self.buf) == l:
1440 s, self.buf = str(self.buf), ''
1440 s, self.buf = str(self.buf), ''
1441 else:
1441 else:
1442 s, self.buf = self.buf[:l], buffer(self.buf, l)
1442 s, self.buf = self.buf[:l], buffer(self.buf, l)
1443 return s
1443 return s
1444
1444
1445 def filechunkiter(f, size=65536, limit=None):
1445 def filechunkiter(f, size=65536, limit=None):
1446 """Create a generator that produces the data in the file size
1446 """Create a generator that produces the data in the file size
1447 (default 65536) bytes at a time, up to optional limit (default is
1447 (default 65536) bytes at a time, up to optional limit (default is
1448 to read all data). Chunks may be less than size bytes if the
1448 to read all data). Chunks may be less than size bytes if the
1449 chunk is the last chunk in the file, or the file is a socket or
1449 chunk is the last chunk in the file, or the file is a socket or
1450 some other type of file that sometimes reads less data than is
1450 some other type of file that sometimes reads less data than is
1451 requested."""
1451 requested."""
1452 assert size >= 0
1452 assert size >= 0
1453 assert limit is None or limit >= 0
1453 assert limit is None or limit >= 0
1454 while True:
1454 while True:
1455 if limit is None: nbytes = size
1455 if limit is None: nbytes = size
1456 else: nbytes = min(limit, size)
1456 else: nbytes = min(limit, size)
1457 s = nbytes and f.read(nbytes)
1457 s = nbytes and f.read(nbytes)
1458 if not s: break
1458 if not s: break
1459 if limit: limit -= len(s)
1459 if limit: limit -= len(s)
1460 yield s
1460 yield s
1461
1461
1462 def makedate():
1462 def makedate():
1463 lt = time.localtime()
1463 lt = time.localtime()
1464 if lt[8] == 1 and time.daylight:
1464 if lt[8] == 1 and time.daylight:
1465 tz = time.altzone
1465 tz = time.altzone
1466 else:
1466 else:
1467 tz = time.timezone
1467 tz = time.timezone
1468 return time.mktime(lt), tz
1468 return time.mktime(lt), tz
1469
1469
1470 def datestr(date=None, format='%a %b %d %H:%M:%S %Y', timezone=True, timezone_format=" %+03d%02d"):
1470 def datestr(date=None, format='%a %b %d %H:%M:%S %Y', timezone=True, timezone_format=" %+03d%02d"):
1471 """represent a (unixtime, offset) tuple as a localized time.
1471 """represent a (unixtime, offset) tuple as a localized time.
1472 unixtime is seconds since the epoch, and offset is the time zone's
1472 unixtime is seconds since the epoch, and offset is the time zone's
1473 number of seconds away from UTC. if timezone is false, do not
1473 number of seconds away from UTC. if timezone is false, do not
1474 append time zone to string."""
1474 append time zone to string."""
1475 t, tz = date or makedate()
1475 t, tz = date or makedate()
1476 s = time.strftime(format, time.gmtime(float(t) - tz))
1476 s = time.strftime(format, time.gmtime(float(t) - tz))
1477 if timezone:
1477 if timezone:
1478 s += timezone_format % (-tz / 3600, ((-tz % 3600) / 60))
1478 s += timezone_format % (-tz / 3600, ((-tz % 3600) / 60))
1479 return s
1479 return s
1480
1480
1481 def strdate(string, format, defaults=[]):
1481 def strdate(string, format, defaults=[]):
1482 """parse a localized time string and return a (unixtime, offset) tuple.
1482 """parse a localized time string and return a (unixtime, offset) tuple.
1483 if the string cannot be parsed, ValueError is raised."""
1483 if the string cannot be parsed, ValueError is raised."""
1484 def timezone(string):
1484 def timezone(string):
1485 tz = string.split()[-1]
1485 tz = string.split()[-1]
1486 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
1486 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
1487 tz = int(tz)
1487 tz = int(tz)
1488 offset = - 3600 * (tz / 100) - 60 * (tz % 100)
1488 offset = - 3600 * (tz / 100) - 60 * (tz % 100)
1489 return offset
1489 return offset
1490 if tz == "GMT" or tz == "UTC":
1490 if tz == "GMT" or tz == "UTC":
1491 return 0
1491 return 0
1492 return None
1492 return None
1493
1493
1494 # NOTE: unixtime = localunixtime + offset
1494 # NOTE: unixtime = localunixtime + offset
1495 offset, date = timezone(string), string
1495 offset, date = timezone(string), string
1496 if offset != None:
1496 if offset != None:
1497 date = " ".join(string.split()[:-1])
1497 date = " ".join(string.split()[:-1])
1498
1498
1499 # add missing elements from defaults
1499 # add missing elements from defaults
1500 for part in defaults:
1500 for part in defaults:
1501 found = [True for p in part if ("%"+p) in format]
1501 found = [True for p in part if ("%"+p) in format]
1502 if not found:
1502 if not found:
1503 date += "@" + defaults[part]
1503 date += "@" + defaults[part]
1504 format += "@%" + part[0]
1504 format += "@%" + part[0]
1505
1505
1506 timetuple = time.strptime(date, format)
1506 timetuple = time.strptime(date, format)
1507 localunixtime = int(calendar.timegm(timetuple))
1507 localunixtime = int(calendar.timegm(timetuple))
1508 if offset is None:
1508 if offset is None:
1509 # local timezone
1509 # local timezone
1510 unixtime = int(time.mktime(timetuple))
1510 unixtime = int(time.mktime(timetuple))
1511 offset = unixtime - localunixtime
1511 offset = unixtime - localunixtime
1512 else:
1512 else:
1513 unixtime = localunixtime + offset
1513 unixtime = localunixtime + offset
1514 return unixtime, offset
1514 return unixtime, offset
1515
1515
1516 def parsedate(string, formats=None, defaults=None):
1516 def parsedate(string, formats=None, defaults=None):
1517 """parse a localized time string and return a (unixtime, offset) tuple.
1517 """parse a localized time string and return a (unixtime, offset) tuple.
1518 The date may be a "unixtime offset" string or in one of the specified
1518 The date may be a "unixtime offset" string or in one of the specified
1519 formats."""
1519 formats."""
1520 if not string:
1520 if not string:
1521 return 0, 0
1521 return 0, 0
1522 if not formats:
1522 if not formats:
1523 formats = defaultdateformats
1523 formats = defaultdateformats
1524 string = string.strip()
1524 string = string.strip()
1525 try:
1525 try:
1526 when, offset = map(int, string.split(' '))
1526 when, offset = map(int, string.split(' '))
1527 except ValueError:
1527 except ValueError:
1528 # fill out defaults
1528 # fill out defaults
1529 if not defaults:
1529 if not defaults:
1530 defaults = {}
1530 defaults = {}
1531 now = makedate()
1531 now = makedate()
1532 for part in "d mb yY HI M S".split():
1532 for part in "d mb yY HI M S".split():
1533 if part not in defaults:
1533 if part not in defaults:
1534 if part[0] in "HMS":
1534 if part[0] in "HMS":
1535 defaults[part] = "00"
1535 defaults[part] = "00"
1536 elif part[0] in "dm":
1536 elif part[0] in "dm":
1537 defaults[part] = "1"
1537 defaults[part] = "1"
1538 else:
1538 else:
1539 defaults[part] = datestr(now, "%" + part[0], False)
1539 defaults[part] = datestr(now, "%" + part[0], False)
1540
1540
1541 for format in formats:
1541 for format in formats:
1542 try:
1542 try:
1543 when, offset = strdate(string, format, defaults)
1543 when, offset = strdate(string, format, defaults)
1544 except ValueError:
1544 except (ValueError, OverflowError):
1545 pass
1545 pass
1546 else:
1546 else:
1547 break
1547 break
1548 else:
1548 else:
1549 raise Abort(_('invalid date: %r ') % string)
1549 raise Abort(_('invalid date: %r ') % string)
1550 # validate explicit (probably user-specified) date and
1550 # validate explicit (probably user-specified) date and
1551 # time zone offset. values must fit in signed 32 bits for
1551 # time zone offset. values must fit in signed 32 bits for
1552 # current 32-bit linux runtimes. timezones go from UTC-12
1552 # current 32-bit linux runtimes. timezones go from UTC-12
1553 # to UTC+14
1553 # to UTC+14
1554 if abs(when) > 0x7fffffff:
1554 if abs(when) > 0x7fffffff:
1555 raise Abort(_('date exceeds 32 bits: %d') % when)
1555 raise Abort(_('date exceeds 32 bits: %d') % when)
1556 if offset < -50400 or offset > 43200:
1556 if offset < -50400 or offset > 43200:
1557 raise Abort(_('impossible time zone offset: %d') % offset)
1557 raise Abort(_('impossible time zone offset: %d') % offset)
1558 return when, offset
1558 return when, offset
1559
1559
1560 def matchdate(date):
1560 def matchdate(date):
1561 """Return a function that matches a given date match specifier
1561 """Return a function that matches a given date match specifier
1562
1562
1563 Formats include:
1563 Formats include:
1564
1564
1565 '{date}' match a given date to the accuracy provided
1565 '{date}' match a given date to the accuracy provided
1566
1566
1567 '<{date}' on or before a given date
1567 '<{date}' on or before a given date
1568
1568
1569 '>{date}' on or after a given date
1569 '>{date}' on or after a given date
1570
1570
1571 """
1571 """
1572
1572
1573 def lower(date):
1573 def lower(date):
1574 return parsedate(date, extendeddateformats)[0]
1574 return parsedate(date, extendeddateformats)[0]
1575
1575
1576 def upper(date):
1576 def upper(date):
1577 d = dict(mb="12", HI="23", M="59", S="59")
1577 d = dict(mb="12", HI="23", M="59", S="59")
1578 for days in "31 30 29".split():
1578 for days in "31 30 29".split():
1579 try:
1579 try:
1580 d["d"] = days
1580 d["d"] = days
1581 return parsedate(date, extendeddateformats, d)[0]
1581 return parsedate(date, extendeddateformats, d)[0]
1582 except:
1582 except:
1583 pass
1583 pass
1584 d["d"] = "28"
1584 d["d"] = "28"
1585 return parsedate(date, extendeddateformats, d)[0]
1585 return parsedate(date, extendeddateformats, d)[0]
1586
1586
1587 if date[0] == "<":
1587 if date[0] == "<":
1588 when = upper(date[1:])
1588 when = upper(date[1:])
1589 return lambda x: x <= when
1589 return lambda x: x <= when
1590 elif date[0] == ">":
1590 elif date[0] == ">":
1591 when = lower(date[1:])
1591 when = lower(date[1:])
1592 return lambda x: x >= when
1592 return lambda x: x >= when
1593 elif date[0] == "-":
1593 elif date[0] == "-":
1594 try:
1594 try:
1595 days = int(date[1:])
1595 days = int(date[1:])
1596 except ValueError:
1596 except ValueError:
1597 raise Abort(_("invalid day spec: %s") % date[1:])
1597 raise Abort(_("invalid day spec: %s") % date[1:])
1598 when = makedate()[0] - days * 3600 * 24
1598 when = makedate()[0] - days * 3600 * 24
1599 return lambda x: x >= when
1599 return lambda x: x >= when
1600 elif " to " in date:
1600 elif " to " in date:
1601 a, b = date.split(" to ")
1601 a, b = date.split(" to ")
1602 start, stop = lower(a), upper(b)
1602 start, stop = lower(a), upper(b)
1603 return lambda x: x >= start and x <= stop
1603 return lambda x: x >= start and x <= stop
1604 else:
1604 else:
1605 start, stop = lower(date), upper(date)
1605 start, stop = lower(date), upper(date)
1606 return lambda x: x >= start and x <= stop
1606 return lambda x: x >= start and x <= stop
1607
1607
1608 def shortuser(user):
1608 def shortuser(user):
1609 """Return a short representation of a user name or email address."""
1609 """Return a short representation of a user name or email address."""
1610 f = user.find('@')
1610 f = user.find('@')
1611 if f >= 0:
1611 if f >= 0:
1612 user = user[:f]
1612 user = user[:f]
1613 f = user.find('<')
1613 f = user.find('<')
1614 if f >= 0:
1614 if f >= 0:
1615 user = user[f+1:]
1615 user = user[f+1:]
1616 f = user.find(' ')
1616 f = user.find(' ')
1617 if f >= 0:
1617 if f >= 0:
1618 user = user[:f]
1618 user = user[:f]
1619 f = user.find('.')
1619 f = user.find('.')
1620 if f >= 0:
1620 if f >= 0:
1621 user = user[:f]
1621 user = user[:f]
1622 return user
1622 return user
1623
1623
1624 def ellipsis(text, maxlength=400):
1624 def ellipsis(text, maxlength=400):
1625 """Trim string to at most maxlength (default: 400) characters."""
1625 """Trim string to at most maxlength (default: 400) characters."""
1626 if len(text) <= maxlength:
1626 if len(text) <= maxlength:
1627 return text
1627 return text
1628 else:
1628 else:
1629 return "%s..." % (text[:maxlength-3])
1629 return "%s..." % (text[:maxlength-3])
1630
1630
1631 def walkrepos(path):
1631 def walkrepos(path):
1632 '''yield every hg repository under path, recursively.'''
1632 '''yield every hg repository under path, recursively.'''
1633 def errhandler(err):
1633 def errhandler(err):
1634 if err.filename == path:
1634 if err.filename == path:
1635 raise err
1635 raise err
1636
1636
1637 for root, dirs, files in os.walk(path, onerror=errhandler):
1637 for root, dirs, files in os.walk(path, onerror=errhandler):
1638 for d in dirs:
1638 for d in dirs:
1639 if d == '.hg':
1639 if d == '.hg':
1640 yield root
1640 yield root
1641 dirs[:] = []
1641 dirs[:] = []
1642 break
1642 break
1643
1643
1644 _rcpath = None
1644 _rcpath = None
1645
1645
1646 def os_rcpath():
1646 def os_rcpath():
1647 '''return default os-specific hgrc search path'''
1647 '''return default os-specific hgrc search path'''
1648 path = system_rcpath()
1648 path = system_rcpath()
1649 path.extend(user_rcpath())
1649 path.extend(user_rcpath())
1650 path = [os.path.normpath(f) for f in path]
1650 path = [os.path.normpath(f) for f in path]
1651 return path
1651 return path
1652
1652
1653 def rcpath():
1653 def rcpath():
1654 '''return hgrc search path. if env var HGRCPATH is set, use it.
1654 '''return hgrc search path. if env var HGRCPATH is set, use it.
1655 for each item in path, if directory, use files ending in .rc,
1655 for each item in path, if directory, use files ending in .rc,
1656 else use item.
1656 else use item.
1657 make HGRCPATH empty to only look in .hg/hgrc of current repo.
1657 make HGRCPATH empty to only look in .hg/hgrc of current repo.
1658 if no HGRCPATH, use default os-specific path.'''
1658 if no HGRCPATH, use default os-specific path.'''
1659 global _rcpath
1659 global _rcpath
1660 if _rcpath is None:
1660 if _rcpath is None:
1661 if 'HGRCPATH' in os.environ:
1661 if 'HGRCPATH' in os.environ:
1662 _rcpath = []
1662 _rcpath = []
1663 for p in os.environ['HGRCPATH'].split(os.pathsep):
1663 for p in os.environ['HGRCPATH'].split(os.pathsep):
1664 if not p: continue
1664 if not p: continue
1665 if os.path.isdir(p):
1665 if os.path.isdir(p):
1666 for f, kind in osutil.listdir(p):
1666 for f, kind in osutil.listdir(p):
1667 if f.endswith('.rc'):
1667 if f.endswith('.rc'):
1668 _rcpath.append(os.path.join(p, f))
1668 _rcpath.append(os.path.join(p, f))
1669 else:
1669 else:
1670 _rcpath.append(p)
1670 _rcpath.append(p)
1671 else:
1671 else:
1672 _rcpath = os_rcpath()
1672 _rcpath = os_rcpath()
1673 return _rcpath
1673 return _rcpath
1674
1674
1675 def bytecount(nbytes):
1675 def bytecount(nbytes):
1676 '''return byte count formatted as readable string, with units'''
1676 '''return byte count formatted as readable string, with units'''
1677
1677
1678 units = (
1678 units = (
1679 (100, 1<<30, _('%.0f GB')),
1679 (100, 1<<30, _('%.0f GB')),
1680 (10, 1<<30, _('%.1f GB')),
1680 (10, 1<<30, _('%.1f GB')),
1681 (1, 1<<30, _('%.2f GB')),
1681 (1, 1<<30, _('%.2f GB')),
1682 (100, 1<<20, _('%.0f MB')),
1682 (100, 1<<20, _('%.0f MB')),
1683 (10, 1<<20, _('%.1f MB')),
1683 (10, 1<<20, _('%.1f MB')),
1684 (1, 1<<20, _('%.2f MB')),
1684 (1, 1<<20, _('%.2f MB')),
1685 (100, 1<<10, _('%.0f KB')),
1685 (100, 1<<10, _('%.0f KB')),
1686 (10, 1<<10, _('%.1f KB')),
1686 (10, 1<<10, _('%.1f KB')),
1687 (1, 1<<10, _('%.2f KB')),
1687 (1, 1<<10, _('%.2f KB')),
1688 (1, 1, _('%.0f bytes')),
1688 (1, 1, _('%.0f bytes')),
1689 )
1689 )
1690
1690
1691 for multiplier, divisor, format in units:
1691 for multiplier, divisor, format in units:
1692 if nbytes >= divisor * multiplier:
1692 if nbytes >= divisor * multiplier:
1693 return format % (nbytes / float(divisor))
1693 return format % (nbytes / float(divisor))
1694 return units[-1][2] % nbytes
1694 return units[-1][2] % nbytes
1695
1695
1696 def drop_scheme(scheme, path):
1696 def drop_scheme(scheme, path):
1697 sc = scheme + ':'
1697 sc = scheme + ':'
1698 if path.startswith(sc):
1698 if path.startswith(sc):
1699 path = path[len(sc):]
1699 path = path[len(sc):]
1700 if path.startswith('//'):
1700 if path.startswith('//'):
1701 path = path[2:]
1701 path = path[2:]
1702 return path
1702 return path
1703
1703
1704 def uirepr(s):
1704 def uirepr(s):
1705 # Avoid double backslash in Windows path repr()
1705 # Avoid double backslash in Windows path repr()
1706 return repr(s).replace('\\\\', '\\')
1706 return repr(s).replace('\\\\', '\\')
1707
1707
1708 def hidepassword(url):
1708 def hidepassword(url):
1709 '''replaces the password in the url string by three asterisks (***)
1709 '''replaces the password in the url string by three asterisks (***)
1710
1710
1711 >>> hidepassword('http://www.example.com/some/path#fragment')
1711 >>> hidepassword('http://www.example.com/some/path#fragment')
1712 'http://www.example.com/some/path#fragment'
1712 'http://www.example.com/some/path#fragment'
1713 >>> hidepassword('http://me@www.example.com/some/path#fragment')
1713 >>> hidepassword('http://me@www.example.com/some/path#fragment')
1714 'http://me@www.example.com/some/path#fragment'
1714 'http://me@www.example.com/some/path#fragment'
1715 >>> hidepassword('http://me:simplepw@www.example.com/path#frag')
1715 >>> hidepassword('http://me:simplepw@www.example.com/path#frag')
1716 'http://me:***@www.example.com/path#frag'
1716 'http://me:***@www.example.com/path#frag'
1717 >>> hidepassword('http://me:complex:pw@www.example.com/path#frag')
1717 >>> hidepassword('http://me:complex:pw@www.example.com/path#frag')
1718 'http://me:***@www.example.com/path#frag'
1718 'http://me:***@www.example.com/path#frag'
1719 >>> hidepassword('/path/to/repo')
1719 >>> hidepassword('/path/to/repo')
1720 '/path/to/repo'
1720 '/path/to/repo'
1721 >>> hidepassword('relative/path/to/repo')
1721 >>> hidepassword('relative/path/to/repo')
1722 'relative/path/to/repo'
1722 'relative/path/to/repo'
1723 >>> hidepassword('c:\\\\path\\\\to\\\\repo')
1723 >>> hidepassword('c:\\\\path\\\\to\\\\repo')
1724 'c:\\\\path\\\\to\\\\repo'
1724 'c:\\\\path\\\\to\\\\repo'
1725 >>> hidepassword('c:/path/to/repo')
1725 >>> hidepassword('c:/path/to/repo')
1726 'c:/path/to/repo'
1726 'c:/path/to/repo'
1727 >>> hidepassword('bundle://path/to/bundle')
1727 >>> hidepassword('bundle://path/to/bundle')
1728 'bundle://path/to/bundle'
1728 'bundle://path/to/bundle'
1729 '''
1729 '''
1730 url_parts = list(urlparse.urlparse(url))
1730 url_parts = list(urlparse.urlparse(url))
1731 host_with_pw_pattern = re.compile('^([^:]*):([^@]*)@(.*)$')
1731 host_with_pw_pattern = re.compile('^([^:]*):([^@]*)@(.*)$')
1732 if host_with_pw_pattern.match(url_parts[1]):
1732 if host_with_pw_pattern.match(url_parts[1]):
1733 url_parts[1] = re.sub(host_with_pw_pattern, r'\1:***@\3',
1733 url_parts[1] = re.sub(host_with_pw_pattern, r'\1:***@\3',
1734 url_parts[1])
1734 url_parts[1])
1735 return urlparse.urlunparse(url_parts)
1735 return urlparse.urlunparse(url_parts)
1736
1736
General Comments 0
You need to be logged in to leave comments. Login now