##// END OF EJS Templates
add util.lexists
Alexis S. L. Carvalho -
r4281:384672d8 default
parent child Browse files
Show More
@@ -1,1475 +1,1484 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, 2006 Matt Mackall <mpm@selenic.com>
5 Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
6 Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
6 Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
7
7
8 This software may be used and distributed according to the terms
8 This software may be used and distributed according to the terms
9 of the GNU General Public License, incorporated herein by reference.
9 of the GNU General Public License, incorporated herein by reference.
10
10
11 This contains helper routines that are independent of the SCM core and hide
11 This contains helper routines that are independent of the SCM core and hide
12 platform-specific details from the core.
12 platform-specific details from the core.
13 """
13 """
14
14
15 from i18n import _
15 from i18n import _
16 import cStringIO, errno, getpass, popen2, re, shutil, sys, tempfile
16 import cStringIO, errno, getpass, popen2, re, shutil, sys, tempfile
17 import os, threading, time, calendar, ConfigParser, locale, glob
17 import os, threading, time, calendar, ConfigParser, locale, glob
18
18
19 try:
19 try:
20 _encoding = os.environ.get("HGENCODING") or locale.getpreferredencoding() \
20 _encoding = os.environ.get("HGENCODING") or locale.getpreferredencoding() \
21 or "ascii"
21 or "ascii"
22 except locale.Error:
22 except locale.Error:
23 _encoding = 'ascii'
23 _encoding = 'ascii'
24 _encodingmode = os.environ.get("HGENCODINGMODE", "strict")
24 _encodingmode = os.environ.get("HGENCODINGMODE", "strict")
25 _fallbackencoding = 'ISO-8859-1'
25 _fallbackencoding = 'ISO-8859-1'
26
26
27 def tolocal(s):
27 def tolocal(s):
28 """
28 """
29 Convert a string from internal UTF-8 to local encoding
29 Convert a string from internal UTF-8 to local encoding
30
30
31 All internal strings should be UTF-8 but some repos before the
31 All internal strings should be UTF-8 but some repos before the
32 implementation of locale support may contain latin1 or possibly
32 implementation of locale support may contain latin1 or possibly
33 other character sets. We attempt to decode everything strictly
33 other character sets. We attempt to decode everything strictly
34 using UTF-8, then Latin-1, and failing that, we use UTF-8 and
34 using UTF-8, then Latin-1, and failing that, we use UTF-8 and
35 replace unknown characters.
35 replace unknown characters.
36 """
36 """
37 for e in ('UTF-8', _fallbackencoding):
37 for e in ('UTF-8', _fallbackencoding):
38 try:
38 try:
39 u = s.decode(e) # attempt strict decoding
39 u = s.decode(e) # attempt strict decoding
40 return u.encode(_encoding, "replace")
40 return u.encode(_encoding, "replace")
41 except LookupError, k:
41 except LookupError, k:
42 raise Abort(_("%s, please check your locale settings") % k)
42 raise Abort(_("%s, please check your locale settings") % k)
43 except UnicodeDecodeError:
43 except UnicodeDecodeError:
44 pass
44 pass
45 u = s.decode("utf-8", "replace") # last ditch
45 u = s.decode("utf-8", "replace") # last ditch
46 return u.encode(_encoding, "replace")
46 return u.encode(_encoding, "replace")
47
47
48 def fromlocal(s):
48 def fromlocal(s):
49 """
49 """
50 Convert a string from the local character encoding to UTF-8
50 Convert a string from the local character encoding to UTF-8
51
51
52 We attempt to decode strings using the encoding mode set by
52 We attempt to decode strings using the encoding mode set by
53 HG_ENCODINGMODE, which defaults to 'strict'. In this mode, unknown
53 HG_ENCODINGMODE, which defaults to 'strict'. In this mode, unknown
54 characters will cause an error message. Other modes include
54 characters will cause an error message. Other modes include
55 'replace', which replaces unknown characters with a special
55 'replace', which replaces unknown characters with a special
56 Unicode character, and 'ignore', which drops the character.
56 Unicode character, and 'ignore', which drops the character.
57 """
57 """
58 try:
58 try:
59 return s.decode(_encoding, _encodingmode).encode("utf-8")
59 return s.decode(_encoding, _encodingmode).encode("utf-8")
60 except UnicodeDecodeError, inst:
60 except UnicodeDecodeError, inst:
61 sub = s[max(0, inst.start-10):inst.start+10]
61 sub = s[max(0, inst.start-10):inst.start+10]
62 raise Abort("decoding near '%s': %s!" % (sub, inst))
62 raise Abort("decoding near '%s': %s!" % (sub, inst))
63 except LookupError, k:
63 except LookupError, k:
64 raise Abort(_("%s, please check your locale settings") % k)
64 raise Abort(_("%s, please check your locale settings") % k)
65
65
66 def locallen(s):
66 def locallen(s):
67 """Find the length in characters of a local string"""
67 """Find the length in characters of a local string"""
68 return len(s.decode(_encoding, "replace"))
68 return len(s.decode(_encoding, "replace"))
69
69
70 def localsub(s, a, b=None):
70 def localsub(s, a, b=None):
71 try:
71 try:
72 u = s.decode(_encoding, _encodingmode)
72 u = s.decode(_encoding, _encodingmode)
73 if b is not None:
73 if b is not None:
74 u = u[a:b]
74 u = u[a:b]
75 else:
75 else:
76 u = u[:a]
76 u = u[:a]
77 return u.encode(_encoding, _encodingmode)
77 return u.encode(_encoding, _encodingmode)
78 except UnicodeDecodeError, inst:
78 except UnicodeDecodeError, inst:
79 sub = s[max(0, inst.start-10), inst.start+10]
79 sub = s[max(0, inst.start-10), inst.start+10]
80 raise Abort(_("decoding near '%s': %s!\n") % (sub, inst))
80 raise Abort(_("decoding near '%s': %s!\n") % (sub, inst))
81
81
82 # used by parsedate
82 # used by parsedate
83 defaultdateformats = (
83 defaultdateformats = (
84 '%Y-%m-%d %H:%M:%S',
84 '%Y-%m-%d %H:%M:%S',
85 '%Y-%m-%d %I:%M:%S%p',
85 '%Y-%m-%d %I:%M:%S%p',
86 '%Y-%m-%d %H:%M',
86 '%Y-%m-%d %H:%M',
87 '%Y-%m-%d %I:%M%p',
87 '%Y-%m-%d %I:%M%p',
88 '%Y-%m-%d',
88 '%Y-%m-%d',
89 '%m-%d',
89 '%m-%d',
90 '%m/%d',
90 '%m/%d',
91 '%m/%d/%y',
91 '%m/%d/%y',
92 '%m/%d/%Y',
92 '%m/%d/%Y',
93 '%a %b %d %H:%M:%S %Y',
93 '%a %b %d %H:%M:%S %Y',
94 '%a %b %d %I:%M:%S%p %Y',
94 '%a %b %d %I:%M:%S%p %Y',
95 '%b %d %H:%M:%S %Y',
95 '%b %d %H:%M:%S %Y',
96 '%b %d %I:%M:%S%p %Y',
96 '%b %d %I:%M:%S%p %Y',
97 '%b %d %H:%M:%S',
97 '%b %d %H:%M:%S',
98 '%b %d %I:%M:%S%p',
98 '%b %d %I:%M:%S%p',
99 '%b %d %H:%M',
99 '%b %d %H:%M',
100 '%b %d %I:%M%p',
100 '%b %d %I:%M%p',
101 '%b %d %Y',
101 '%b %d %Y',
102 '%b %d',
102 '%b %d',
103 '%H:%M:%S',
103 '%H:%M:%S',
104 '%I:%M:%SP',
104 '%I:%M:%SP',
105 '%H:%M',
105 '%H:%M',
106 '%I:%M%p',
106 '%I:%M%p',
107 )
107 )
108
108
109 extendeddateformats = defaultdateformats + (
109 extendeddateformats = defaultdateformats + (
110 "%Y",
110 "%Y",
111 "%Y-%m",
111 "%Y-%m",
112 "%b",
112 "%b",
113 "%b %Y",
113 "%b %Y",
114 )
114 )
115
115
116 class SignalInterrupt(Exception):
116 class SignalInterrupt(Exception):
117 """Exception raised on SIGTERM and SIGHUP."""
117 """Exception raised on SIGTERM and SIGHUP."""
118
118
119 # differences from SafeConfigParser:
119 # differences from SafeConfigParser:
120 # - case-sensitive keys
120 # - case-sensitive keys
121 # - allows values that are not strings (this means that you may not
121 # - allows values that are not strings (this means that you may not
122 # be able to save the configuration to a file)
122 # be able to save the configuration to a file)
123 class configparser(ConfigParser.SafeConfigParser):
123 class configparser(ConfigParser.SafeConfigParser):
124 def optionxform(self, optionstr):
124 def optionxform(self, optionstr):
125 return optionstr
125 return optionstr
126
126
127 def set(self, section, option, value):
127 def set(self, section, option, value):
128 return ConfigParser.ConfigParser.set(self, section, option, value)
128 return ConfigParser.ConfigParser.set(self, section, option, value)
129
129
130 def _interpolate(self, section, option, rawval, vars):
130 def _interpolate(self, section, option, rawval, vars):
131 if not isinstance(rawval, basestring):
131 if not isinstance(rawval, basestring):
132 return rawval
132 return rawval
133 return ConfigParser.SafeConfigParser._interpolate(self, section,
133 return ConfigParser.SafeConfigParser._interpolate(self, section,
134 option, rawval, vars)
134 option, rawval, vars)
135
135
136 def cachefunc(func):
136 def cachefunc(func):
137 '''cache the result of function calls'''
137 '''cache the result of function calls'''
138 # XXX doesn't handle keywords args
138 # XXX doesn't handle keywords args
139 cache = {}
139 cache = {}
140 if func.func_code.co_argcount == 1:
140 if func.func_code.co_argcount == 1:
141 # we gain a small amount of time because
141 # we gain a small amount of time because
142 # we don't need to pack/unpack the list
142 # we don't need to pack/unpack the list
143 def f(arg):
143 def f(arg):
144 if arg not in cache:
144 if arg not in cache:
145 cache[arg] = func(arg)
145 cache[arg] = func(arg)
146 return cache[arg]
146 return cache[arg]
147 else:
147 else:
148 def f(*args):
148 def f(*args):
149 if args not in cache:
149 if args not in cache:
150 cache[args] = func(*args)
150 cache[args] = func(*args)
151 return cache[args]
151 return cache[args]
152
152
153 return f
153 return f
154
154
155 def pipefilter(s, cmd):
155 def pipefilter(s, cmd):
156 '''filter string S through command CMD, returning its output'''
156 '''filter string S through command CMD, returning its output'''
157 (pout, pin) = popen2.popen2(cmd, -1, 'b')
157 (pout, pin) = popen2.popen2(cmd, -1, 'b')
158 def writer():
158 def writer():
159 try:
159 try:
160 pin.write(s)
160 pin.write(s)
161 pin.close()
161 pin.close()
162 except IOError, inst:
162 except IOError, inst:
163 if inst.errno != errno.EPIPE:
163 if inst.errno != errno.EPIPE:
164 raise
164 raise
165
165
166 # we should use select instead on UNIX, but this will work on most
166 # we should use select instead on UNIX, but this will work on most
167 # systems, including Windows
167 # systems, including Windows
168 w = threading.Thread(target=writer)
168 w = threading.Thread(target=writer)
169 w.start()
169 w.start()
170 f = pout.read()
170 f = pout.read()
171 pout.close()
171 pout.close()
172 w.join()
172 w.join()
173 return f
173 return f
174
174
175 def tempfilter(s, cmd):
175 def tempfilter(s, cmd):
176 '''filter string S through a pair of temporary files with CMD.
176 '''filter string S through a pair of temporary files with CMD.
177 CMD is used as a template to create the real command to be run,
177 CMD is used as a template to create the real command to be run,
178 with the strings INFILE and OUTFILE replaced by the real names of
178 with the strings INFILE and OUTFILE replaced by the real names of
179 the temporary files generated.'''
179 the temporary files generated.'''
180 inname, outname = None, None
180 inname, outname = None, None
181 try:
181 try:
182 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
182 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
183 fp = os.fdopen(infd, 'wb')
183 fp = os.fdopen(infd, 'wb')
184 fp.write(s)
184 fp.write(s)
185 fp.close()
185 fp.close()
186 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
186 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
187 os.close(outfd)
187 os.close(outfd)
188 cmd = cmd.replace('INFILE', inname)
188 cmd = cmd.replace('INFILE', inname)
189 cmd = cmd.replace('OUTFILE', outname)
189 cmd = cmd.replace('OUTFILE', outname)
190 code = os.system(cmd)
190 code = os.system(cmd)
191 if code: raise Abort(_("command '%s' failed: %s") %
191 if code: raise Abort(_("command '%s' failed: %s") %
192 (cmd, explain_exit(code)))
192 (cmd, explain_exit(code)))
193 return open(outname, 'rb').read()
193 return open(outname, 'rb').read()
194 finally:
194 finally:
195 try:
195 try:
196 if inname: os.unlink(inname)
196 if inname: os.unlink(inname)
197 except: pass
197 except: pass
198 try:
198 try:
199 if outname: os.unlink(outname)
199 if outname: os.unlink(outname)
200 except: pass
200 except: pass
201
201
202 filtertable = {
202 filtertable = {
203 'tempfile:': tempfilter,
203 'tempfile:': tempfilter,
204 'pipe:': pipefilter,
204 'pipe:': pipefilter,
205 }
205 }
206
206
207 def filter(s, cmd):
207 def filter(s, cmd):
208 "filter a string through a command that transforms its input to its output"
208 "filter a string through a command that transforms its input to its output"
209 for name, fn in filtertable.iteritems():
209 for name, fn in filtertable.iteritems():
210 if cmd.startswith(name):
210 if cmd.startswith(name):
211 return fn(s, cmd[len(name):].lstrip())
211 return fn(s, cmd[len(name):].lstrip())
212 return pipefilter(s, cmd)
212 return pipefilter(s, cmd)
213
213
214 def find_in_path(name, path, default=None):
214 def find_in_path(name, path, default=None):
215 '''find name in search path. path can be string (will be split
215 '''find name in search path. path can be string (will be split
216 with os.pathsep), or iterable thing that returns strings. if name
216 with os.pathsep), or iterable thing that returns strings. if name
217 found, return path to name. else return default.'''
217 found, return path to name. else return default.'''
218 if isinstance(path, str):
218 if isinstance(path, str):
219 path = path.split(os.pathsep)
219 path = path.split(os.pathsep)
220 for p in path:
220 for p in path:
221 p_name = os.path.join(p, name)
221 p_name = os.path.join(p, name)
222 if os.path.exists(p_name):
222 if os.path.exists(p_name):
223 return p_name
223 return p_name
224 return default
224 return default
225
225
226 def binary(s):
226 def binary(s):
227 """return true if a string is binary data using diff's heuristic"""
227 """return true if a string is binary data using diff's heuristic"""
228 if s and '\0' in s[:4096]:
228 if s and '\0' in s[:4096]:
229 return True
229 return True
230 return False
230 return False
231
231
232 def unique(g):
232 def unique(g):
233 """return the uniq elements of iterable g"""
233 """return the uniq elements of iterable g"""
234 seen = {}
234 seen = {}
235 l = []
235 l = []
236 for f in g:
236 for f in g:
237 if f not in seen:
237 if f not in seen:
238 seen[f] = 1
238 seen[f] = 1
239 l.append(f)
239 l.append(f)
240 return l
240 return l
241
241
242 class Abort(Exception):
242 class Abort(Exception):
243 """Raised if a command needs to print an error and exit."""
243 """Raised if a command needs to print an error and exit."""
244
244
245 class UnexpectedOutput(Abort):
245 class UnexpectedOutput(Abort):
246 """Raised to print an error with part of output and exit."""
246 """Raised to print an error with part of output and exit."""
247
247
248 def always(fn): return True
248 def always(fn): return True
249 def never(fn): return False
249 def never(fn): return False
250
250
251 def expand_glob(pats):
251 def expand_glob(pats):
252 '''On Windows, expand the implicit globs in a list of patterns'''
252 '''On Windows, expand the implicit globs in a list of patterns'''
253 if os.name != 'nt':
253 if os.name != 'nt':
254 return list(pats)
254 return list(pats)
255 ret = []
255 ret = []
256 for p in pats:
256 for p in pats:
257 kind, name = patkind(p, None)
257 kind, name = patkind(p, None)
258 if kind is None:
258 if kind is None:
259 globbed = glob.glob(name)
259 globbed = glob.glob(name)
260 if globbed:
260 if globbed:
261 ret.extend(globbed)
261 ret.extend(globbed)
262 continue
262 continue
263 # if we couldn't expand the glob, just keep it around
263 # if we couldn't expand the glob, just keep it around
264 ret.append(p)
264 ret.append(p)
265 return ret
265 return ret
266
266
267 def patkind(name, dflt_pat='glob'):
267 def patkind(name, dflt_pat='glob'):
268 """Split a string into an optional pattern kind prefix and the
268 """Split a string into an optional pattern kind prefix and the
269 actual pattern."""
269 actual pattern."""
270 for prefix in 're', 'glob', 'path', 'relglob', 'relpath', 'relre':
270 for prefix in 're', 'glob', 'path', 'relglob', 'relpath', 'relre':
271 if name.startswith(prefix + ':'): return name.split(':', 1)
271 if name.startswith(prefix + ':'): return name.split(':', 1)
272 return dflt_pat, name
272 return dflt_pat, name
273
273
274 def globre(pat, head='^', tail='$'):
274 def globre(pat, head='^', tail='$'):
275 "convert a glob pattern into a regexp"
275 "convert a glob pattern into a regexp"
276 i, n = 0, len(pat)
276 i, n = 0, len(pat)
277 res = ''
277 res = ''
278 group = False
278 group = False
279 def peek(): return i < n and pat[i]
279 def peek(): return i < n and pat[i]
280 while i < n:
280 while i < n:
281 c = pat[i]
281 c = pat[i]
282 i = i+1
282 i = i+1
283 if c == '*':
283 if c == '*':
284 if peek() == '*':
284 if peek() == '*':
285 i += 1
285 i += 1
286 res += '.*'
286 res += '.*'
287 else:
287 else:
288 res += '[^/]*'
288 res += '[^/]*'
289 elif c == '?':
289 elif c == '?':
290 res += '.'
290 res += '.'
291 elif c == '[':
291 elif c == '[':
292 j = i
292 j = i
293 if j < n and pat[j] in '!]':
293 if j < n and pat[j] in '!]':
294 j += 1
294 j += 1
295 while j < n and pat[j] != ']':
295 while j < n and pat[j] != ']':
296 j += 1
296 j += 1
297 if j >= n:
297 if j >= n:
298 res += '\\['
298 res += '\\['
299 else:
299 else:
300 stuff = pat[i:j].replace('\\','\\\\')
300 stuff = pat[i:j].replace('\\','\\\\')
301 i = j + 1
301 i = j + 1
302 if stuff[0] == '!':
302 if stuff[0] == '!':
303 stuff = '^' + stuff[1:]
303 stuff = '^' + stuff[1:]
304 elif stuff[0] == '^':
304 elif stuff[0] == '^':
305 stuff = '\\' + stuff
305 stuff = '\\' + stuff
306 res = '%s[%s]' % (res, stuff)
306 res = '%s[%s]' % (res, stuff)
307 elif c == '{':
307 elif c == '{':
308 group = True
308 group = True
309 res += '(?:'
309 res += '(?:'
310 elif c == '}' and group:
310 elif c == '}' and group:
311 res += ')'
311 res += ')'
312 group = False
312 group = False
313 elif c == ',' and group:
313 elif c == ',' and group:
314 res += '|'
314 res += '|'
315 elif c == '\\':
315 elif c == '\\':
316 p = peek()
316 p = peek()
317 if p:
317 if p:
318 i += 1
318 i += 1
319 res += re.escape(p)
319 res += re.escape(p)
320 else:
320 else:
321 res += re.escape(c)
321 res += re.escape(c)
322 else:
322 else:
323 res += re.escape(c)
323 res += re.escape(c)
324 return head + res + tail
324 return head + res + tail
325
325
326 _globchars = {'[': 1, '{': 1, '*': 1, '?': 1}
326 _globchars = {'[': 1, '{': 1, '*': 1, '?': 1}
327
327
328 def pathto(root, n1, n2):
328 def pathto(root, n1, n2):
329 '''return the relative path from one place to another.
329 '''return the relative path from one place to another.
330 root should use os.sep to separate directories
330 root should use os.sep to separate directories
331 n1 should use os.sep to separate directories
331 n1 should use os.sep to separate directories
332 n2 should use "/" to separate directories
332 n2 should use "/" to separate directories
333 returns an os.sep-separated path.
333 returns an os.sep-separated path.
334
334
335 If n1 is a relative path, it's assumed it's
335 If n1 is a relative path, it's assumed it's
336 relative to root.
336 relative to root.
337 n2 should always be relative to root.
337 n2 should always be relative to root.
338 '''
338 '''
339 if not n1: return localpath(n2)
339 if not n1: return localpath(n2)
340 if os.path.isabs(n1):
340 if os.path.isabs(n1):
341 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
341 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
342 return os.path.join(root, localpath(n2))
342 return os.path.join(root, localpath(n2))
343 n2 = '/'.join((pconvert(root), n2))
343 n2 = '/'.join((pconvert(root), n2))
344 a, b = n1.split(os.sep), n2.split('/')
344 a, b = n1.split(os.sep), n2.split('/')
345 a.reverse()
345 a.reverse()
346 b.reverse()
346 b.reverse()
347 while a and b and a[-1] == b[-1]:
347 while a and b and a[-1] == b[-1]:
348 a.pop()
348 a.pop()
349 b.pop()
349 b.pop()
350 b.reverse()
350 b.reverse()
351 return os.sep.join((['..'] * len(a)) + b)
351 return os.sep.join((['..'] * len(a)) + b)
352
352
353 def canonpath(root, cwd, myname):
353 def canonpath(root, cwd, myname):
354 """return the canonical path of myname, given cwd and root"""
354 """return the canonical path of myname, given cwd and root"""
355 if root == os.sep:
355 if root == os.sep:
356 rootsep = os.sep
356 rootsep = os.sep
357 elif root.endswith(os.sep):
357 elif root.endswith(os.sep):
358 rootsep = root
358 rootsep = root
359 else:
359 else:
360 rootsep = root + os.sep
360 rootsep = root + os.sep
361 name = myname
361 name = myname
362 if not os.path.isabs(name):
362 if not os.path.isabs(name):
363 name = os.path.join(root, cwd, name)
363 name = os.path.join(root, cwd, name)
364 name = os.path.normpath(name)
364 name = os.path.normpath(name)
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):
450 def regex(kind, name):
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, '(?:|.*/)', '(?:/|$)')
459 return globre(name, '(?:|.*/)', '(?:/|$)')
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, '', '(?:/|$)')
466 return globre(name, '', '(?:/|$)')
467
467
468 def matchfn(pats):
468 def matchfn(pats):
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 matches = []
472 matches = []
473 for k, p in pats:
473 for k, p in pats:
474 try:
474 try:
475 pat = '(?:%s)' % regex(k, p)
475 pat = '(?:%s)' % regex(k, p)
476 matches.append(re.compile(pat).match)
476 matches.append(re.compile(pat).match)
477 except re.error:
477 except re.error:
478 if src: raise Abort("%s: invalid pattern (%s): %s" % (src, k, p))
478 if src: raise Abort("%s: invalid pattern (%s): %s" % (src, k, p))
479 else: raise Abort("invalid pattern (%s): %s" % (k, p))
479 else: raise Abort("invalid pattern (%s): %s" % (k, p))
480
480
481 def buildfn(text):
481 def buildfn(text):
482 for m in matches:
482 for m in matches:
483 r = m(text)
483 r = m(text)
484 if r:
484 if r:
485 return r
485 return r
486
486
487 return buildfn
487 return buildfn
488
488
489 def globprefix(pat):
489 def globprefix(pat):
490 '''return the non-glob prefix of a path, e.g. foo/* -> foo'''
490 '''return the non-glob prefix of a path, e.g. foo/* -> foo'''
491 root = []
491 root = []
492 for p in pat.split('/'):
492 for p in pat.split('/'):
493 if contains_glob(p): break
493 if contains_glob(p): break
494 root.append(p)
494 root.append(p)
495 return '/'.join(root) or '.'
495 return '/'.join(root) or '.'
496
496
497 def normalizepats(names, default):
497 def normalizepats(names, default):
498 pats = []
498 pats = []
499 roots = []
499 roots = []
500 anypats = False
500 anypats = False
501 for kind, name in [patkind(p, default) for p in names]:
501 for kind, name in [patkind(p, default) for p in names]:
502 if kind in ('glob', 'relpath'):
502 if kind in ('glob', 'relpath'):
503 name = canonpath(canonroot, cwd, name)
503 name = canonpath(canonroot, cwd, name)
504 elif kind in ('relglob', 'path'):
504 elif kind in ('relglob', 'path'):
505 name = normpath(name)
505 name = normpath(name)
506
506
507 pats.append((kind, name))
507 pats.append((kind, name))
508
508
509 if kind in ('glob', 're', 'relglob', 'relre'):
509 if kind in ('glob', 're', 'relglob', 'relre'):
510 anypats = True
510 anypats = True
511
511
512 if kind == 'glob':
512 if kind == 'glob':
513 root = globprefix(name)
513 root = globprefix(name)
514 roots.append(root)
514 roots.append(root)
515 elif kind in ('relpath', 'path'):
515 elif kind in ('relpath', 'path'):
516 roots.append(name or '.')
516 roots.append(name or '.')
517 elif kind == 'relglob':
517 elif kind == 'relglob':
518 roots.append('.')
518 roots.append('.')
519 return roots, pats, anypats
519 return roots, pats, anypats
520
520
521 roots, pats, anypats = normalizepats(names, dflt_pat)
521 roots, pats, anypats = normalizepats(names, dflt_pat)
522
522
523 patmatch = matchfn(pats) or always
523 patmatch = matchfn(pats) or always
524 incmatch = always
524 incmatch = always
525 if inc:
525 if inc:
526 dummy, inckinds, dummy = normalizepats(inc, 'glob')
526 dummy, inckinds, dummy = normalizepats(inc, 'glob')
527 incmatch = matchfn(inckinds)
527 incmatch = matchfn(inckinds)
528 excmatch = lambda fn: False
528 excmatch = lambda fn: False
529 if exc:
529 if exc:
530 dummy, exckinds, dummy = normalizepats(exc, 'glob')
530 dummy, exckinds, dummy = normalizepats(exc, 'glob')
531 excmatch = matchfn(exckinds)
531 excmatch = matchfn(exckinds)
532
532
533 if not names and inc and not exc:
533 if not names and inc and not exc:
534 # common case: hgignore patterns
534 # common case: hgignore patterns
535 match = incmatch
535 match = incmatch
536 else:
536 else:
537 match = lambda fn: incmatch(fn) and not excmatch(fn) and patmatch(fn)
537 match = lambda fn: incmatch(fn) and not excmatch(fn) and patmatch(fn)
538
538
539 return (roots, match, (inc or exc or anypats) and True)
539 return (roots, match, (inc or exc or anypats) and True)
540
540
541 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None):
541 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None):
542 '''enhanced shell command execution.
542 '''enhanced shell command execution.
543 run with environment maybe modified, maybe in different dir.
543 run with environment maybe modified, maybe in different dir.
544
544
545 if command fails and onerr is None, return status. if ui object,
545 if command fails and onerr is None, return status. if ui object,
546 print error message and return status, else raise onerr object as
546 print error message and return status, else raise onerr object as
547 exception.'''
547 exception.'''
548 def py2shell(val):
548 def py2shell(val):
549 'convert python object into string that is useful to shell'
549 'convert python object into string that is useful to shell'
550 if val in (None, False):
550 if val in (None, False):
551 return '0'
551 return '0'
552 if val == True:
552 if val == True:
553 return '1'
553 return '1'
554 return str(val)
554 return str(val)
555 oldenv = {}
555 oldenv = {}
556 for k in environ:
556 for k in environ:
557 oldenv[k] = os.environ.get(k)
557 oldenv[k] = os.environ.get(k)
558 if cwd is not None:
558 if cwd is not None:
559 oldcwd = os.getcwd()
559 oldcwd = os.getcwd()
560 origcmd = cmd
560 origcmd = cmd
561 if os.name == 'nt':
561 if os.name == 'nt':
562 cmd = '"%s"' % cmd
562 cmd = '"%s"' % cmd
563 try:
563 try:
564 for k, v in environ.iteritems():
564 for k, v in environ.iteritems():
565 os.environ[k] = py2shell(v)
565 os.environ[k] = py2shell(v)
566 if cwd is not None and oldcwd != cwd:
566 if cwd is not None and oldcwd != cwd:
567 os.chdir(cwd)
567 os.chdir(cwd)
568 rc = os.system(cmd)
568 rc = os.system(cmd)
569 if rc and onerr:
569 if rc and onerr:
570 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
570 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
571 explain_exit(rc)[0])
571 explain_exit(rc)[0])
572 if errprefix:
572 if errprefix:
573 errmsg = '%s: %s' % (errprefix, errmsg)
573 errmsg = '%s: %s' % (errprefix, errmsg)
574 try:
574 try:
575 onerr.warn(errmsg + '\n')
575 onerr.warn(errmsg + '\n')
576 except AttributeError:
576 except AttributeError:
577 raise onerr(errmsg)
577 raise onerr(errmsg)
578 return rc
578 return rc
579 finally:
579 finally:
580 for k, v in oldenv.iteritems():
580 for k, v in oldenv.iteritems():
581 if v is None:
581 if v is None:
582 del os.environ[k]
582 del os.environ[k]
583 else:
583 else:
584 os.environ[k] = v
584 os.environ[k] = v
585 if cwd is not None and oldcwd != cwd:
585 if cwd is not None and oldcwd != cwd:
586 os.chdir(oldcwd)
586 os.chdir(oldcwd)
587
587
588 # os.path.lexists is not available on python2.3
589 def lexists(filename):
590 "test whether a file with this name exists. does not follow symlinks"
591 try:
592 os.lstat(filename)
593 except:
594 return False
595 return True
596
588 def rename(src, dst):
597 def rename(src, dst):
589 """forcibly rename a file"""
598 """forcibly rename a file"""
590 try:
599 try:
591 os.rename(src, dst)
600 os.rename(src, dst)
592 except OSError, err:
601 except OSError, err:
593 # on windows, rename to existing file is not allowed, so we
602 # on windows, rename to existing file is not allowed, so we
594 # must delete destination first. but if file is open, unlink
603 # must delete destination first. but if file is open, unlink
595 # schedules it for delete but does not delete it. rename
604 # schedules it for delete but does not delete it. rename
596 # happens immediately even for open files, so we create
605 # happens immediately even for open files, so we create
597 # temporary file, delete it, rename destination to that name,
606 # temporary file, delete it, rename destination to that name,
598 # then delete that. then rename is safe to do.
607 # then delete that. then rename is safe to do.
599 fd, temp = tempfile.mkstemp(dir=os.path.dirname(dst) or '.')
608 fd, temp = tempfile.mkstemp(dir=os.path.dirname(dst) or '.')
600 os.close(fd)
609 os.close(fd)
601 os.unlink(temp)
610 os.unlink(temp)
602 os.rename(dst, temp)
611 os.rename(dst, temp)
603 os.unlink(temp)
612 os.unlink(temp)
604 os.rename(src, dst)
613 os.rename(src, dst)
605
614
606 def unlink(f):
615 def unlink(f):
607 """unlink and remove the directory if it is empty"""
616 """unlink and remove the directory if it is empty"""
608 os.unlink(f)
617 os.unlink(f)
609 # try removing directories that might now be empty
618 # try removing directories that might now be empty
610 try:
619 try:
611 os.removedirs(os.path.dirname(f))
620 os.removedirs(os.path.dirname(f))
612 except OSError:
621 except OSError:
613 pass
622 pass
614
623
615 def copyfile(src, dest):
624 def copyfile(src, dest):
616 "copy a file, preserving mode"
625 "copy a file, preserving mode"
617 if os.path.islink(src):
626 if os.path.islink(src):
618 try:
627 try:
619 os.unlink(dest)
628 os.unlink(dest)
620 except:
629 except:
621 pass
630 pass
622 os.symlink(os.readlink(src), dest)
631 os.symlink(os.readlink(src), dest)
623 else:
632 else:
624 try:
633 try:
625 shutil.copyfile(src, dest)
634 shutil.copyfile(src, dest)
626 shutil.copymode(src, dest)
635 shutil.copymode(src, dest)
627 except shutil.Error, inst:
636 except shutil.Error, inst:
628 raise Abort(str(inst))
637 raise Abort(str(inst))
629
638
630 def copyfiles(src, dst, hardlink=None):
639 def copyfiles(src, dst, hardlink=None):
631 """Copy a directory tree using hardlinks if possible"""
640 """Copy a directory tree using hardlinks if possible"""
632
641
633 if hardlink is None:
642 if hardlink is None:
634 hardlink = (os.stat(src).st_dev ==
643 hardlink = (os.stat(src).st_dev ==
635 os.stat(os.path.dirname(dst)).st_dev)
644 os.stat(os.path.dirname(dst)).st_dev)
636
645
637 if os.path.isdir(src):
646 if os.path.isdir(src):
638 os.mkdir(dst)
647 os.mkdir(dst)
639 for name in os.listdir(src):
648 for name in os.listdir(src):
640 srcname = os.path.join(src, name)
649 srcname = os.path.join(src, name)
641 dstname = os.path.join(dst, name)
650 dstname = os.path.join(dst, name)
642 copyfiles(srcname, dstname, hardlink)
651 copyfiles(srcname, dstname, hardlink)
643 else:
652 else:
644 if hardlink:
653 if hardlink:
645 try:
654 try:
646 os_link(src, dst)
655 os_link(src, dst)
647 except (IOError, OSError):
656 except (IOError, OSError):
648 hardlink = False
657 hardlink = False
649 shutil.copy(src, dst)
658 shutil.copy(src, dst)
650 else:
659 else:
651 shutil.copy(src, dst)
660 shutil.copy(src, dst)
652
661
653 def audit_path(path):
662 def audit_path(path):
654 """Abort if path contains dangerous components"""
663 """Abort if path contains dangerous components"""
655 parts = os.path.normcase(path).split(os.sep)
664 parts = os.path.normcase(path).split(os.sep)
656 if (os.path.splitdrive(path)[0] or parts[0] in ('.hg', '')
665 if (os.path.splitdrive(path)[0] or parts[0] in ('.hg', '')
657 or os.pardir in parts):
666 or os.pardir in parts):
658 raise Abort(_("path contains illegal component: %s\n") % path)
667 raise Abort(_("path contains illegal component: %s\n") % path)
659
668
660 def _makelock_file(info, pathname):
669 def _makelock_file(info, pathname):
661 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
670 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
662 os.write(ld, info)
671 os.write(ld, info)
663 os.close(ld)
672 os.close(ld)
664
673
665 def _readlock_file(pathname):
674 def _readlock_file(pathname):
666 return posixfile(pathname).read()
675 return posixfile(pathname).read()
667
676
668 def nlinks(pathname):
677 def nlinks(pathname):
669 """Return number of hardlinks for the given file."""
678 """Return number of hardlinks for the given file."""
670 return os.lstat(pathname).st_nlink
679 return os.lstat(pathname).st_nlink
671
680
672 if hasattr(os, 'link'):
681 if hasattr(os, 'link'):
673 os_link = os.link
682 os_link = os.link
674 else:
683 else:
675 def os_link(src, dst):
684 def os_link(src, dst):
676 raise OSError(0, _("Hardlinks not supported"))
685 raise OSError(0, _("Hardlinks not supported"))
677
686
678 def fstat(fp):
687 def fstat(fp):
679 '''stat file object that may not have fileno method.'''
688 '''stat file object that may not have fileno method.'''
680 try:
689 try:
681 return os.fstat(fp.fileno())
690 return os.fstat(fp.fileno())
682 except AttributeError:
691 except AttributeError:
683 return os.stat(fp.name)
692 return os.stat(fp.name)
684
693
685 posixfile = file
694 posixfile = file
686
695
687 def is_win_9x():
696 def is_win_9x():
688 '''return true if run on windows 95, 98 or me.'''
697 '''return true if run on windows 95, 98 or me.'''
689 try:
698 try:
690 return sys.getwindowsversion()[3] == 1
699 return sys.getwindowsversion()[3] == 1
691 except AttributeError:
700 except AttributeError:
692 return os.name == 'nt' and 'command' in os.environ.get('comspec', '')
701 return os.name == 'nt' and 'command' in os.environ.get('comspec', '')
693
702
694 getuser_fallback = None
703 getuser_fallback = None
695
704
696 def getuser():
705 def getuser():
697 '''return name of current user'''
706 '''return name of current user'''
698 try:
707 try:
699 return getpass.getuser()
708 return getpass.getuser()
700 except ImportError:
709 except ImportError:
701 # import of pwd will fail on windows - try fallback
710 # import of pwd will fail on windows - try fallback
702 if getuser_fallback:
711 if getuser_fallback:
703 return getuser_fallback()
712 return getuser_fallback()
704 # raised if win32api not available
713 # raised if win32api not available
705 raise Abort(_('user name not available - set USERNAME '
714 raise Abort(_('user name not available - set USERNAME '
706 'environment variable'))
715 'environment variable'))
707
716
708 def username(uid=None):
717 def username(uid=None):
709 """Return the name of the user with the given uid.
718 """Return the name of the user with the given uid.
710
719
711 If uid is None, return the name of the current user."""
720 If uid is None, return the name of the current user."""
712 try:
721 try:
713 import pwd
722 import pwd
714 if uid is None:
723 if uid is None:
715 uid = os.getuid()
724 uid = os.getuid()
716 try:
725 try:
717 return pwd.getpwuid(uid)[0]
726 return pwd.getpwuid(uid)[0]
718 except KeyError:
727 except KeyError:
719 return str(uid)
728 return str(uid)
720 except ImportError:
729 except ImportError:
721 return None
730 return None
722
731
723 def groupname(gid=None):
732 def groupname(gid=None):
724 """Return the name of the group with the given gid.
733 """Return the name of the group with the given gid.
725
734
726 If gid is None, return the name of the current group."""
735 If gid is None, return the name of the current group."""
727 try:
736 try:
728 import grp
737 import grp
729 if gid is None:
738 if gid is None:
730 gid = os.getgid()
739 gid = os.getgid()
731 try:
740 try:
732 return grp.getgrgid(gid)[0]
741 return grp.getgrgid(gid)[0]
733 except KeyError:
742 except KeyError:
734 return str(gid)
743 return str(gid)
735 except ImportError:
744 except ImportError:
736 return None
745 return None
737
746
738 # File system features
747 # File system features
739
748
740 def checkfolding(path):
749 def checkfolding(path):
741 """
750 """
742 Check whether the given path is on a case-sensitive filesystem
751 Check whether the given path is on a case-sensitive filesystem
743
752
744 Requires a path (like /foo/.hg) ending with a foldable final
753 Requires a path (like /foo/.hg) ending with a foldable final
745 directory component.
754 directory component.
746 """
755 """
747 s1 = os.stat(path)
756 s1 = os.stat(path)
748 d, b = os.path.split(path)
757 d, b = os.path.split(path)
749 p2 = os.path.join(d, b.upper())
758 p2 = os.path.join(d, b.upper())
750 if path == p2:
759 if path == p2:
751 p2 = os.path.join(d, b.lower())
760 p2 = os.path.join(d, b.lower())
752 try:
761 try:
753 s2 = os.stat(p2)
762 s2 = os.stat(p2)
754 if s2 == s1:
763 if s2 == s1:
755 return False
764 return False
756 return True
765 return True
757 except:
766 except:
758 return True
767 return True
759
768
760 def checkexec(path):
769 def checkexec(path):
761 """
770 """
762 Check whether the given path is on a filesystem with UNIX-like exec flags
771 Check whether the given path is on a filesystem with UNIX-like exec flags
763
772
764 Requires a directory (like /foo/.hg)
773 Requires a directory (like /foo/.hg)
765 """
774 """
766 fh, fn = tempfile.mkstemp("", "", path)
775 fh, fn = tempfile.mkstemp("", "", path)
767 os.close(fh)
776 os.close(fh)
768 m = os.stat(fn).st_mode
777 m = os.stat(fn).st_mode
769 os.chmod(fn, m ^ 0111)
778 os.chmod(fn, m ^ 0111)
770 r = (os.stat(fn).st_mode != m)
779 r = (os.stat(fn).st_mode != m)
771 os.unlink(fn)
780 os.unlink(fn)
772 return r
781 return r
773
782
774 def execfunc(path, fallback):
783 def execfunc(path, fallback):
775 '''return an is_exec() function with default to fallback'''
784 '''return an is_exec() function with default to fallback'''
776 if checkexec(path):
785 if checkexec(path):
777 return lambda x: is_exec(os.path.join(path, x))
786 return lambda x: is_exec(os.path.join(path, x))
778 return fallback
787 return fallback
779
788
780 def checklink(path):
789 def checklink(path):
781 """check whether the given path is on a symlink-capable filesystem"""
790 """check whether the given path is on a symlink-capable filesystem"""
782 # mktemp is not racy because symlink creation will fail if the
791 # mktemp is not racy because symlink creation will fail if the
783 # file already exists
792 # file already exists
784 name = tempfile.mktemp(dir=path)
793 name = tempfile.mktemp(dir=path)
785 try:
794 try:
786 os.symlink(".", name)
795 os.symlink(".", name)
787 os.unlink(name)
796 os.unlink(name)
788 return True
797 return True
789 except (OSError, AttributeError):
798 except (OSError, AttributeError):
790 return False
799 return False
791
800
792 def linkfunc(path, fallback):
801 def linkfunc(path, fallback):
793 '''return an is_link() function with default to fallback'''
802 '''return an is_link() function with default to fallback'''
794 if checklink(path):
803 if checklink(path):
795 return lambda x: os.path.islink(os.path.join(path, x))
804 return lambda x: os.path.islink(os.path.join(path, x))
796 return fallback
805 return fallback
797
806
798 # Platform specific variants
807 # Platform specific variants
799 if os.name == 'nt':
808 if os.name == 'nt':
800 import msvcrt
809 import msvcrt
801 nulldev = 'NUL:'
810 nulldev = 'NUL:'
802
811
803 class winstdout:
812 class winstdout:
804 '''stdout on windows misbehaves if sent through a pipe'''
813 '''stdout on windows misbehaves if sent through a pipe'''
805
814
806 def __init__(self, fp):
815 def __init__(self, fp):
807 self.fp = fp
816 self.fp = fp
808
817
809 def __getattr__(self, key):
818 def __getattr__(self, key):
810 return getattr(self.fp, key)
819 return getattr(self.fp, key)
811
820
812 def close(self):
821 def close(self):
813 try:
822 try:
814 self.fp.close()
823 self.fp.close()
815 except: pass
824 except: pass
816
825
817 def write(self, s):
826 def write(self, s):
818 try:
827 try:
819 return self.fp.write(s)
828 return self.fp.write(s)
820 except IOError, inst:
829 except IOError, inst:
821 if inst.errno != 0: raise
830 if inst.errno != 0: raise
822 self.close()
831 self.close()
823 raise IOError(errno.EPIPE, 'Broken pipe')
832 raise IOError(errno.EPIPE, 'Broken pipe')
824
833
825 def flush(self):
834 def flush(self):
826 try:
835 try:
827 return self.fp.flush()
836 return self.fp.flush()
828 except IOError, inst:
837 except IOError, inst:
829 if inst.errno != errno.EINVAL: raise
838 if inst.errno != errno.EINVAL: raise
830 self.close()
839 self.close()
831 raise IOError(errno.EPIPE, 'Broken pipe')
840 raise IOError(errno.EPIPE, 'Broken pipe')
832
841
833 sys.stdout = winstdout(sys.stdout)
842 sys.stdout = winstdout(sys.stdout)
834
843
835 def system_rcpath():
844 def system_rcpath():
836 try:
845 try:
837 return system_rcpath_win32()
846 return system_rcpath_win32()
838 except:
847 except:
839 return [r'c:\mercurial\mercurial.ini']
848 return [r'c:\mercurial\mercurial.ini']
840
849
841 def user_rcpath():
850 def user_rcpath():
842 '''return os-specific hgrc search path to the user dir'''
851 '''return os-specific hgrc search path to the user dir'''
843 try:
852 try:
844 userrc = user_rcpath_win32()
853 userrc = user_rcpath_win32()
845 except:
854 except:
846 userrc = os.path.join(os.path.expanduser('~'), 'mercurial.ini')
855 userrc = os.path.join(os.path.expanduser('~'), 'mercurial.ini')
847 path = [userrc]
856 path = [userrc]
848 userprofile = os.environ.get('USERPROFILE')
857 userprofile = os.environ.get('USERPROFILE')
849 if userprofile:
858 if userprofile:
850 path.append(os.path.join(userprofile, 'mercurial.ini'))
859 path.append(os.path.join(userprofile, 'mercurial.ini'))
851 return path
860 return path
852
861
853 def parse_patch_output(output_line):
862 def parse_patch_output(output_line):
854 """parses the output produced by patch and returns the file name"""
863 """parses the output produced by patch and returns the file name"""
855 pf = output_line[14:]
864 pf = output_line[14:]
856 if pf[0] == '`':
865 if pf[0] == '`':
857 pf = pf[1:-1] # Remove the quotes
866 pf = pf[1:-1] # Remove the quotes
858 return pf
867 return pf
859
868
860 def testpid(pid):
869 def testpid(pid):
861 '''return False if pid dead, True if running or not known'''
870 '''return False if pid dead, True if running or not known'''
862 return True
871 return True
863
872
864 def set_exec(f, mode):
873 def set_exec(f, mode):
865 pass
874 pass
866
875
867 def set_link(f, mode):
876 def set_link(f, mode):
868 pass
877 pass
869
878
870 def set_binary(fd):
879 def set_binary(fd):
871 msvcrt.setmode(fd.fileno(), os.O_BINARY)
880 msvcrt.setmode(fd.fileno(), os.O_BINARY)
872
881
873 def pconvert(path):
882 def pconvert(path):
874 return path.replace("\\", "/")
883 return path.replace("\\", "/")
875
884
876 def localpath(path):
885 def localpath(path):
877 return path.replace('/', '\\')
886 return path.replace('/', '\\')
878
887
879 def normpath(path):
888 def normpath(path):
880 return pconvert(os.path.normpath(path))
889 return pconvert(os.path.normpath(path))
881
890
882 makelock = _makelock_file
891 makelock = _makelock_file
883 readlock = _readlock_file
892 readlock = _readlock_file
884
893
885 def samestat(s1, s2):
894 def samestat(s1, s2):
886 return False
895 return False
887
896
888 # A sequence of backslashes is special iff it precedes a double quote:
897 # A sequence of backslashes is special iff it precedes a double quote:
889 # - if there's an even number of backslashes, the double quote is not
898 # - if there's an even number of backslashes, the double quote is not
890 # quoted (i.e. it ends the quoted region)
899 # quoted (i.e. it ends the quoted region)
891 # - if there's an odd number of backslashes, the double quote is quoted
900 # - if there's an odd number of backslashes, the double quote is quoted
892 # - in both cases, every pair of backslashes is unquoted into a single
901 # - in both cases, every pair of backslashes is unquoted into a single
893 # backslash
902 # backslash
894 # (See http://msdn2.microsoft.com/en-us/library/a1y7w461.aspx )
903 # (See http://msdn2.microsoft.com/en-us/library/a1y7w461.aspx )
895 # So, to quote a string, we must surround it in double quotes, double
904 # So, to quote a string, we must surround it in double quotes, double
896 # the number of backslashes that preceed double quotes and add another
905 # the number of backslashes that preceed double quotes and add another
897 # backslash before every double quote (being careful with the double
906 # backslash before every double quote (being careful with the double
898 # quote we've appended to the end)
907 # quote we've appended to the end)
899 _quotere = None
908 _quotere = None
900 def shellquote(s):
909 def shellquote(s):
901 global _quotere
910 global _quotere
902 if _quotere is None:
911 if _quotere is None:
903 _quotere = re.compile(r'(\\*)("|\\$)')
912 _quotere = re.compile(r'(\\*)("|\\$)')
904 return '"%s"' % _quotere.sub(r'\1\1\\\2', s)
913 return '"%s"' % _quotere.sub(r'\1\1\\\2', s)
905
914
906 def explain_exit(code):
915 def explain_exit(code):
907 return _("exited with status %d") % code, code
916 return _("exited with status %d") % code, code
908
917
909 # if you change this stub into a real check, please try to implement the
918 # if you change this stub into a real check, please try to implement the
910 # username and groupname functions above, too.
919 # username and groupname functions above, too.
911 def isowner(fp, st=None):
920 def isowner(fp, st=None):
912 return True
921 return True
913
922
914 try:
923 try:
915 # override functions with win32 versions if possible
924 # override functions with win32 versions if possible
916 from util_win32 import *
925 from util_win32 import *
917 if not is_win_9x():
926 if not is_win_9x():
918 posixfile = posixfile_nt
927 posixfile = posixfile_nt
919 except ImportError:
928 except ImportError:
920 pass
929 pass
921
930
922 else:
931 else:
923 nulldev = '/dev/null'
932 nulldev = '/dev/null'
924 _umask = os.umask(0)
933 _umask = os.umask(0)
925 os.umask(_umask)
934 os.umask(_umask)
926
935
927 def rcfiles(path):
936 def rcfiles(path):
928 rcs = [os.path.join(path, 'hgrc')]
937 rcs = [os.path.join(path, 'hgrc')]
929 rcdir = os.path.join(path, 'hgrc.d')
938 rcdir = os.path.join(path, 'hgrc.d')
930 try:
939 try:
931 rcs.extend([os.path.join(rcdir, f) for f in os.listdir(rcdir)
940 rcs.extend([os.path.join(rcdir, f) for f in os.listdir(rcdir)
932 if f.endswith(".rc")])
941 if f.endswith(".rc")])
933 except OSError:
942 except OSError:
934 pass
943 pass
935 return rcs
944 return rcs
936
945
937 def system_rcpath():
946 def system_rcpath():
938 path = []
947 path = []
939 # old mod_python does not set sys.argv
948 # old mod_python does not set sys.argv
940 if len(getattr(sys, 'argv', [])) > 0:
949 if len(getattr(sys, 'argv', [])) > 0:
941 path.extend(rcfiles(os.path.dirname(sys.argv[0]) +
950 path.extend(rcfiles(os.path.dirname(sys.argv[0]) +
942 '/../etc/mercurial'))
951 '/../etc/mercurial'))
943 path.extend(rcfiles('/etc/mercurial'))
952 path.extend(rcfiles('/etc/mercurial'))
944 return path
953 return path
945
954
946 def user_rcpath():
955 def user_rcpath():
947 return [os.path.expanduser('~/.hgrc')]
956 return [os.path.expanduser('~/.hgrc')]
948
957
949 def parse_patch_output(output_line):
958 def parse_patch_output(output_line):
950 """parses the output produced by patch and returns the file name"""
959 """parses the output produced by patch and returns the file name"""
951 pf = output_line[14:]
960 pf = output_line[14:]
952 if pf.startswith("'") and pf.endswith("'") and " " in pf:
961 if pf.startswith("'") and pf.endswith("'") and " " in pf:
953 pf = pf[1:-1] # Remove the quotes
962 pf = pf[1:-1] # Remove the quotes
954 return pf
963 return pf
955
964
956 def is_exec(f):
965 def is_exec(f):
957 """check whether a file is executable"""
966 """check whether a file is executable"""
958 return (os.lstat(f).st_mode & 0100 != 0)
967 return (os.lstat(f).st_mode & 0100 != 0)
959
968
960 def set_exec(f, mode):
969 def set_exec(f, mode):
961 s = os.lstat(f).st_mode
970 s = os.lstat(f).st_mode
962 if (s & 0100 != 0) == mode:
971 if (s & 0100 != 0) == mode:
963 return
972 return
964 if mode:
973 if mode:
965 # Turn on +x for every +r bit when making a file executable
974 # Turn on +x for every +r bit when making a file executable
966 # and obey umask.
975 # and obey umask.
967 os.chmod(f, s | (s & 0444) >> 2 & ~_umask)
976 os.chmod(f, s | (s & 0444) >> 2 & ~_umask)
968 else:
977 else:
969 os.chmod(f, s & 0666)
978 os.chmod(f, s & 0666)
970
979
971 def set_link(f, mode):
980 def set_link(f, mode):
972 """make a file a symbolic link/regular file
981 """make a file a symbolic link/regular file
973
982
974 if a file is changed to a link, its contents become the link data
983 if a file is changed to a link, its contents become the link data
975 if a link is changed to a file, its link data become its contents
984 if a link is changed to a file, its link data become its contents
976 """
985 """
977
986
978 m = os.path.islink(f)
987 m = os.path.islink(f)
979 if m == bool(mode):
988 if m == bool(mode):
980 return
989 return
981
990
982 if mode: # switch file to link
991 if mode: # switch file to link
983 data = file(f).read()
992 data = file(f).read()
984 os.unlink(f)
993 os.unlink(f)
985 os.symlink(data, f)
994 os.symlink(data, f)
986 else:
995 else:
987 data = os.readlink(f)
996 data = os.readlink(f)
988 os.unlink(f)
997 os.unlink(f)
989 file(f, "w").write(data)
998 file(f, "w").write(data)
990
999
991 def set_binary(fd):
1000 def set_binary(fd):
992 pass
1001 pass
993
1002
994 def pconvert(path):
1003 def pconvert(path):
995 return path
1004 return path
996
1005
997 def localpath(path):
1006 def localpath(path):
998 return path
1007 return path
999
1008
1000 normpath = os.path.normpath
1009 normpath = os.path.normpath
1001 samestat = os.path.samestat
1010 samestat = os.path.samestat
1002
1011
1003 def makelock(info, pathname):
1012 def makelock(info, pathname):
1004 try:
1013 try:
1005 os.symlink(info, pathname)
1014 os.symlink(info, pathname)
1006 except OSError, why:
1015 except OSError, why:
1007 if why.errno == errno.EEXIST:
1016 if why.errno == errno.EEXIST:
1008 raise
1017 raise
1009 else:
1018 else:
1010 _makelock_file(info, pathname)
1019 _makelock_file(info, pathname)
1011
1020
1012 def readlock(pathname):
1021 def readlock(pathname):
1013 try:
1022 try:
1014 return os.readlink(pathname)
1023 return os.readlink(pathname)
1015 except OSError, why:
1024 except OSError, why:
1016 if why.errno == errno.EINVAL:
1025 if why.errno == errno.EINVAL:
1017 return _readlock_file(pathname)
1026 return _readlock_file(pathname)
1018 else:
1027 else:
1019 raise
1028 raise
1020
1029
1021 def shellquote(s):
1030 def shellquote(s):
1022 return "'%s'" % s.replace("'", "'\\''")
1031 return "'%s'" % s.replace("'", "'\\''")
1023
1032
1024 def testpid(pid):
1033 def testpid(pid):
1025 '''return False if pid dead, True if running or not sure'''
1034 '''return False if pid dead, True if running or not sure'''
1026 try:
1035 try:
1027 os.kill(pid, 0)
1036 os.kill(pid, 0)
1028 return True
1037 return True
1029 except OSError, inst:
1038 except OSError, inst:
1030 return inst.errno != errno.ESRCH
1039 return inst.errno != errno.ESRCH
1031
1040
1032 def explain_exit(code):
1041 def explain_exit(code):
1033 """return a 2-tuple (desc, code) describing a process's status"""
1042 """return a 2-tuple (desc, code) describing a process's status"""
1034 if os.WIFEXITED(code):
1043 if os.WIFEXITED(code):
1035 val = os.WEXITSTATUS(code)
1044 val = os.WEXITSTATUS(code)
1036 return _("exited with status %d") % val, val
1045 return _("exited with status %d") % val, val
1037 elif os.WIFSIGNALED(code):
1046 elif os.WIFSIGNALED(code):
1038 val = os.WTERMSIG(code)
1047 val = os.WTERMSIG(code)
1039 return _("killed by signal %d") % val, val
1048 return _("killed by signal %d") % val, val
1040 elif os.WIFSTOPPED(code):
1049 elif os.WIFSTOPPED(code):
1041 val = os.WSTOPSIG(code)
1050 val = os.WSTOPSIG(code)
1042 return _("stopped by signal %d") % val, val
1051 return _("stopped by signal %d") % val, val
1043 raise ValueError(_("invalid exit code"))
1052 raise ValueError(_("invalid exit code"))
1044
1053
1045 def isowner(fp, st=None):
1054 def isowner(fp, st=None):
1046 """Return True if the file object f belongs to the current user.
1055 """Return True if the file object f belongs to the current user.
1047
1056
1048 The return value of a util.fstat(f) may be passed as the st argument.
1057 The return value of a util.fstat(f) may be passed as the st argument.
1049 """
1058 """
1050 if st is None:
1059 if st is None:
1051 st = fstat(fp)
1060 st = fstat(fp)
1052 return st.st_uid == os.getuid()
1061 return st.st_uid == os.getuid()
1053
1062
1054 def _buildencodefun():
1063 def _buildencodefun():
1055 e = '_'
1064 e = '_'
1056 win_reserved = [ord(x) for x in '\\:*?"<>|']
1065 win_reserved = [ord(x) for x in '\\:*?"<>|']
1057 cmap = dict([ (chr(x), chr(x)) for x in xrange(127) ])
1066 cmap = dict([ (chr(x), chr(x)) for x in xrange(127) ])
1058 for x in (range(32) + range(126, 256) + win_reserved):
1067 for x in (range(32) + range(126, 256) + win_reserved):
1059 cmap[chr(x)] = "~%02x" % x
1068 cmap[chr(x)] = "~%02x" % x
1060 for x in range(ord("A"), ord("Z")+1) + [ord(e)]:
1069 for x in range(ord("A"), ord("Z")+1) + [ord(e)]:
1061 cmap[chr(x)] = e + chr(x).lower()
1070 cmap[chr(x)] = e + chr(x).lower()
1062 dmap = {}
1071 dmap = {}
1063 for k, v in cmap.iteritems():
1072 for k, v in cmap.iteritems():
1064 dmap[v] = k
1073 dmap[v] = k
1065 def decode(s):
1074 def decode(s):
1066 i = 0
1075 i = 0
1067 while i < len(s):
1076 while i < len(s):
1068 for l in xrange(1, 4):
1077 for l in xrange(1, 4):
1069 try:
1078 try:
1070 yield dmap[s[i:i+l]]
1079 yield dmap[s[i:i+l]]
1071 i += l
1080 i += l
1072 break
1081 break
1073 except KeyError:
1082 except KeyError:
1074 pass
1083 pass
1075 else:
1084 else:
1076 raise KeyError
1085 raise KeyError
1077 return (lambda s: "".join([cmap[c] for c in s]),
1086 return (lambda s: "".join([cmap[c] for c in s]),
1078 lambda s: "".join(list(decode(s))))
1087 lambda s: "".join(list(decode(s))))
1079
1088
1080 encodefilename, decodefilename = _buildencodefun()
1089 encodefilename, decodefilename = _buildencodefun()
1081
1090
1082 def encodedopener(openerfn, fn):
1091 def encodedopener(openerfn, fn):
1083 def o(path, *args, **kw):
1092 def o(path, *args, **kw):
1084 return openerfn(fn(path), *args, **kw)
1093 return openerfn(fn(path), *args, **kw)
1085 return o
1094 return o
1086
1095
1087 def opener(base, audit=True):
1096 def opener(base, audit=True):
1088 """
1097 """
1089 return a function that opens files relative to base
1098 return a function that opens files relative to base
1090
1099
1091 this function is used to hide the details of COW semantics and
1100 this function is used to hide the details of COW semantics and
1092 remote file access from higher level code.
1101 remote file access from higher level code.
1093 """
1102 """
1094 p = base
1103 p = base
1095 audit_p = audit
1104 audit_p = audit
1096
1105
1097 def mktempcopy(name):
1106 def mktempcopy(name):
1098 d, fn = os.path.split(name)
1107 d, fn = os.path.split(name)
1099 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
1108 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
1100 os.close(fd)
1109 os.close(fd)
1101 ofp = posixfile(temp, "wb")
1110 ofp = posixfile(temp, "wb")
1102 try:
1111 try:
1103 try:
1112 try:
1104 ifp = posixfile(name, "rb")
1113 ifp = posixfile(name, "rb")
1105 except IOError, inst:
1114 except IOError, inst:
1106 if not getattr(inst, 'filename', None):
1115 if not getattr(inst, 'filename', None):
1107 inst.filename = name
1116 inst.filename = name
1108 raise
1117 raise
1109 for chunk in filechunkiter(ifp):
1118 for chunk in filechunkiter(ifp):
1110 ofp.write(chunk)
1119 ofp.write(chunk)
1111 ifp.close()
1120 ifp.close()
1112 ofp.close()
1121 ofp.close()
1113 except:
1122 except:
1114 try: os.unlink(temp)
1123 try: os.unlink(temp)
1115 except: pass
1124 except: pass
1116 raise
1125 raise
1117 st = os.lstat(name)
1126 st = os.lstat(name)
1118 os.chmod(temp, st.st_mode)
1127 os.chmod(temp, st.st_mode)
1119 return temp
1128 return temp
1120
1129
1121 class atomictempfile(posixfile):
1130 class atomictempfile(posixfile):
1122 """the file will only be copied when rename is called"""
1131 """the file will only be copied when rename is called"""
1123 def __init__(self, name, mode):
1132 def __init__(self, name, mode):
1124 self.__name = name
1133 self.__name = name
1125 self.temp = mktempcopy(name)
1134 self.temp = mktempcopy(name)
1126 posixfile.__init__(self, self.temp, mode)
1135 posixfile.__init__(self, self.temp, mode)
1127 def rename(self):
1136 def rename(self):
1128 if not self.closed:
1137 if not self.closed:
1129 posixfile.close(self)
1138 posixfile.close(self)
1130 rename(self.temp, localpath(self.__name))
1139 rename(self.temp, localpath(self.__name))
1131 def __del__(self):
1140 def __del__(self):
1132 if not self.closed:
1141 if not self.closed:
1133 try:
1142 try:
1134 os.unlink(self.temp)
1143 os.unlink(self.temp)
1135 except: pass
1144 except: pass
1136 posixfile.close(self)
1145 posixfile.close(self)
1137
1146
1138 class atomicfile(atomictempfile):
1147 class atomicfile(atomictempfile):
1139 """the file will only be copied on close"""
1148 """the file will only be copied on close"""
1140 def __init__(self, name, mode):
1149 def __init__(self, name, mode):
1141 atomictempfile.__init__(self, name, mode)
1150 atomictempfile.__init__(self, name, mode)
1142 def close(self):
1151 def close(self):
1143 self.rename()
1152 self.rename()
1144 def __del__(self):
1153 def __del__(self):
1145 self.rename()
1154 self.rename()
1146
1155
1147 def o(path, mode="r", text=False, atomic=False, atomictemp=False):
1156 def o(path, mode="r", text=False, atomic=False, atomictemp=False):
1148 if audit_p:
1157 if audit_p:
1149 audit_path(path)
1158 audit_path(path)
1150 f = os.path.join(p, path)
1159 f = os.path.join(p, path)
1151
1160
1152 if not text:
1161 if not text:
1153 mode += "b" # for that other OS
1162 mode += "b" # for that other OS
1154
1163
1155 if mode[0] != "r":
1164 if mode[0] != "r":
1156 try:
1165 try:
1157 nlink = nlinks(f)
1166 nlink = nlinks(f)
1158 except OSError:
1167 except OSError:
1159 d = os.path.dirname(f)
1168 d = os.path.dirname(f)
1160 if not os.path.isdir(d):
1169 if not os.path.isdir(d):
1161 os.makedirs(d)
1170 os.makedirs(d)
1162 else:
1171 else:
1163 if atomic:
1172 if atomic:
1164 return atomicfile(f, mode)
1173 return atomicfile(f, mode)
1165 elif atomictemp:
1174 elif atomictemp:
1166 return atomictempfile(f, mode)
1175 return atomictempfile(f, mode)
1167 if nlink > 1:
1176 if nlink > 1:
1168 rename(mktempcopy(f), f)
1177 rename(mktempcopy(f), f)
1169 return posixfile(f, mode)
1178 return posixfile(f, mode)
1170
1179
1171 return o
1180 return o
1172
1181
1173 class chunkbuffer(object):
1182 class chunkbuffer(object):
1174 """Allow arbitrary sized chunks of data to be efficiently read from an
1183 """Allow arbitrary sized chunks of data to be efficiently read from an
1175 iterator over chunks of arbitrary size."""
1184 iterator over chunks of arbitrary size."""
1176
1185
1177 def __init__(self, in_iter, targetsize = 2**16):
1186 def __init__(self, in_iter, targetsize = 2**16):
1178 """in_iter is the iterator that's iterating over the input chunks.
1187 """in_iter is the iterator that's iterating over the input chunks.
1179 targetsize is how big a buffer to try to maintain."""
1188 targetsize is how big a buffer to try to maintain."""
1180 self.in_iter = iter(in_iter)
1189 self.in_iter = iter(in_iter)
1181 self.buf = ''
1190 self.buf = ''
1182 self.targetsize = int(targetsize)
1191 self.targetsize = int(targetsize)
1183 if self.targetsize <= 0:
1192 if self.targetsize <= 0:
1184 raise ValueError(_("targetsize must be greater than 0, was %d") %
1193 raise ValueError(_("targetsize must be greater than 0, was %d") %
1185 targetsize)
1194 targetsize)
1186 self.iterempty = False
1195 self.iterempty = False
1187
1196
1188 def fillbuf(self):
1197 def fillbuf(self):
1189 """Ignore target size; read every chunk from iterator until empty."""
1198 """Ignore target size; read every chunk from iterator until empty."""
1190 if not self.iterempty:
1199 if not self.iterempty:
1191 collector = cStringIO.StringIO()
1200 collector = cStringIO.StringIO()
1192 collector.write(self.buf)
1201 collector.write(self.buf)
1193 for ch in self.in_iter:
1202 for ch in self.in_iter:
1194 collector.write(ch)
1203 collector.write(ch)
1195 self.buf = collector.getvalue()
1204 self.buf = collector.getvalue()
1196 self.iterempty = True
1205 self.iterempty = True
1197
1206
1198 def read(self, l):
1207 def read(self, l):
1199 """Read L bytes of data from the iterator of chunks of data.
1208 """Read L bytes of data from the iterator of chunks of data.
1200 Returns less than L bytes if the iterator runs dry."""
1209 Returns less than L bytes if the iterator runs dry."""
1201 if l > len(self.buf) and not self.iterempty:
1210 if l > len(self.buf) and not self.iterempty:
1202 # Clamp to a multiple of self.targetsize
1211 # Clamp to a multiple of self.targetsize
1203 targetsize = self.targetsize * ((l // self.targetsize) + 1)
1212 targetsize = self.targetsize * ((l // self.targetsize) + 1)
1204 collector = cStringIO.StringIO()
1213 collector = cStringIO.StringIO()
1205 collector.write(self.buf)
1214 collector.write(self.buf)
1206 collected = len(self.buf)
1215 collected = len(self.buf)
1207 for chunk in self.in_iter:
1216 for chunk in self.in_iter:
1208 collector.write(chunk)
1217 collector.write(chunk)
1209 collected += len(chunk)
1218 collected += len(chunk)
1210 if collected >= targetsize:
1219 if collected >= targetsize:
1211 break
1220 break
1212 if collected < targetsize:
1221 if collected < targetsize:
1213 self.iterempty = True
1222 self.iterempty = True
1214 self.buf = collector.getvalue()
1223 self.buf = collector.getvalue()
1215 s, self.buf = self.buf[:l], buffer(self.buf, l)
1224 s, self.buf = self.buf[:l], buffer(self.buf, l)
1216 return s
1225 return s
1217
1226
1218 def filechunkiter(f, size=65536, limit=None):
1227 def filechunkiter(f, size=65536, limit=None):
1219 """Create a generator that produces the data in the file size
1228 """Create a generator that produces the data in the file size
1220 (default 65536) bytes at a time, up to optional limit (default is
1229 (default 65536) bytes at a time, up to optional limit (default is
1221 to read all data). Chunks may be less than size bytes if the
1230 to read all data). Chunks may be less than size bytes if the
1222 chunk is the last chunk in the file, or the file is a socket or
1231 chunk is the last chunk in the file, or the file is a socket or
1223 some other type of file that sometimes reads less data than is
1232 some other type of file that sometimes reads less data than is
1224 requested."""
1233 requested."""
1225 assert size >= 0
1234 assert size >= 0
1226 assert limit is None or limit >= 0
1235 assert limit is None or limit >= 0
1227 while True:
1236 while True:
1228 if limit is None: nbytes = size
1237 if limit is None: nbytes = size
1229 else: nbytes = min(limit, size)
1238 else: nbytes = min(limit, size)
1230 s = nbytes and f.read(nbytes)
1239 s = nbytes and f.read(nbytes)
1231 if not s: break
1240 if not s: break
1232 if limit: limit -= len(s)
1241 if limit: limit -= len(s)
1233 yield s
1242 yield s
1234
1243
1235 def makedate():
1244 def makedate():
1236 lt = time.localtime()
1245 lt = time.localtime()
1237 if lt[8] == 1 and time.daylight:
1246 if lt[8] == 1 and time.daylight:
1238 tz = time.altzone
1247 tz = time.altzone
1239 else:
1248 else:
1240 tz = time.timezone
1249 tz = time.timezone
1241 return time.mktime(lt), tz
1250 return time.mktime(lt), tz
1242
1251
1243 def datestr(date=None, format='%a %b %d %H:%M:%S %Y', timezone=True):
1252 def datestr(date=None, format='%a %b %d %H:%M:%S %Y', timezone=True):
1244 """represent a (unixtime, offset) tuple as a localized time.
1253 """represent a (unixtime, offset) tuple as a localized time.
1245 unixtime is seconds since the epoch, and offset is the time zone's
1254 unixtime is seconds since the epoch, and offset is the time zone's
1246 number of seconds away from UTC. if timezone is false, do not
1255 number of seconds away from UTC. if timezone is false, do not
1247 append time zone to string."""
1256 append time zone to string."""
1248 t, tz = date or makedate()
1257 t, tz = date or makedate()
1249 s = time.strftime(format, time.gmtime(float(t) - tz))
1258 s = time.strftime(format, time.gmtime(float(t) - tz))
1250 if timezone:
1259 if timezone:
1251 s += " %+03d%02d" % (-tz / 3600, ((-tz % 3600) / 60))
1260 s += " %+03d%02d" % (-tz / 3600, ((-tz % 3600) / 60))
1252 return s
1261 return s
1253
1262
1254 def strdate(string, format, defaults):
1263 def strdate(string, format, defaults):
1255 """parse a localized time string and return a (unixtime, offset) tuple.
1264 """parse a localized time string and return a (unixtime, offset) tuple.
1256 if the string cannot be parsed, ValueError is raised."""
1265 if the string cannot be parsed, ValueError is raised."""
1257 def timezone(string):
1266 def timezone(string):
1258 tz = string.split()[-1]
1267 tz = string.split()[-1]
1259 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
1268 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
1260 tz = int(tz)
1269 tz = int(tz)
1261 offset = - 3600 * (tz / 100) - 60 * (tz % 100)
1270 offset = - 3600 * (tz / 100) - 60 * (tz % 100)
1262 return offset
1271 return offset
1263 if tz == "GMT" or tz == "UTC":
1272 if tz == "GMT" or tz == "UTC":
1264 return 0
1273 return 0
1265 return None
1274 return None
1266
1275
1267 # NOTE: unixtime = localunixtime + offset
1276 # NOTE: unixtime = localunixtime + offset
1268 offset, date = timezone(string), string
1277 offset, date = timezone(string), string
1269 if offset != None:
1278 if offset != None:
1270 date = " ".join(string.split()[:-1])
1279 date = " ".join(string.split()[:-1])
1271
1280
1272 # add missing elements from defaults
1281 # add missing elements from defaults
1273 for part in defaults:
1282 for part in defaults:
1274 found = [True for p in part if ("%"+p) in format]
1283 found = [True for p in part if ("%"+p) in format]
1275 if not found:
1284 if not found:
1276 date += "@" + defaults[part]
1285 date += "@" + defaults[part]
1277 format += "@%" + part[0]
1286 format += "@%" + part[0]
1278
1287
1279 timetuple = time.strptime(date, format)
1288 timetuple = time.strptime(date, format)
1280 localunixtime = int(calendar.timegm(timetuple))
1289 localunixtime = int(calendar.timegm(timetuple))
1281 if offset is None:
1290 if offset is None:
1282 # local timezone
1291 # local timezone
1283 unixtime = int(time.mktime(timetuple))
1292 unixtime = int(time.mktime(timetuple))
1284 offset = unixtime - localunixtime
1293 offset = unixtime - localunixtime
1285 else:
1294 else:
1286 unixtime = localunixtime + offset
1295 unixtime = localunixtime + offset
1287 return unixtime, offset
1296 return unixtime, offset
1288
1297
1289 def parsedate(string, formats=None, defaults=None):
1298 def parsedate(string, formats=None, defaults=None):
1290 """parse a localized time string and return a (unixtime, offset) tuple.
1299 """parse a localized time string and return a (unixtime, offset) tuple.
1291 The date may be a "unixtime offset" string or in one of the specified
1300 The date may be a "unixtime offset" string or in one of the specified
1292 formats."""
1301 formats."""
1293 if not string:
1302 if not string:
1294 return 0, 0
1303 return 0, 0
1295 if not formats:
1304 if not formats:
1296 formats = defaultdateformats
1305 formats = defaultdateformats
1297 string = string.strip()
1306 string = string.strip()
1298 try:
1307 try:
1299 when, offset = map(int, string.split(' '))
1308 when, offset = map(int, string.split(' '))
1300 except ValueError:
1309 except ValueError:
1301 # fill out defaults
1310 # fill out defaults
1302 if not defaults:
1311 if not defaults:
1303 defaults = {}
1312 defaults = {}
1304 now = makedate()
1313 now = makedate()
1305 for part in "d mb yY HI M S".split():
1314 for part in "d mb yY HI M S".split():
1306 if part not in defaults:
1315 if part not in defaults:
1307 if part[0] in "HMS":
1316 if part[0] in "HMS":
1308 defaults[part] = "00"
1317 defaults[part] = "00"
1309 elif part[0] in "dm":
1318 elif part[0] in "dm":
1310 defaults[part] = "1"
1319 defaults[part] = "1"
1311 else:
1320 else:
1312 defaults[part] = datestr(now, "%" + part[0], False)
1321 defaults[part] = datestr(now, "%" + part[0], False)
1313
1322
1314 for format in formats:
1323 for format in formats:
1315 try:
1324 try:
1316 when, offset = strdate(string, format, defaults)
1325 when, offset = strdate(string, format, defaults)
1317 except ValueError:
1326 except ValueError:
1318 pass
1327 pass
1319 else:
1328 else:
1320 break
1329 break
1321 else:
1330 else:
1322 raise Abort(_('invalid date: %r ') % string)
1331 raise Abort(_('invalid date: %r ') % string)
1323 # validate explicit (probably user-specified) date and
1332 # validate explicit (probably user-specified) date and
1324 # time zone offset. values must fit in signed 32 bits for
1333 # time zone offset. values must fit in signed 32 bits for
1325 # current 32-bit linux runtimes. timezones go from UTC-12
1334 # current 32-bit linux runtimes. timezones go from UTC-12
1326 # to UTC+14
1335 # to UTC+14
1327 if abs(when) > 0x7fffffff:
1336 if abs(when) > 0x7fffffff:
1328 raise Abort(_('date exceeds 32 bits: %d') % when)
1337 raise Abort(_('date exceeds 32 bits: %d') % when)
1329 if offset < -50400 or offset > 43200:
1338 if offset < -50400 or offset > 43200:
1330 raise Abort(_('impossible time zone offset: %d') % offset)
1339 raise Abort(_('impossible time zone offset: %d') % offset)
1331 return when, offset
1340 return when, offset
1332
1341
1333 def matchdate(date):
1342 def matchdate(date):
1334 """Return a function that matches a given date match specifier
1343 """Return a function that matches a given date match specifier
1335
1344
1336 Formats include:
1345 Formats include:
1337
1346
1338 '{date}' match a given date to the accuracy provided
1347 '{date}' match a given date to the accuracy provided
1339
1348
1340 '<{date}' on or before a given date
1349 '<{date}' on or before a given date
1341
1350
1342 '>{date}' on or after a given date
1351 '>{date}' on or after a given date
1343
1352
1344 """
1353 """
1345
1354
1346 def lower(date):
1355 def lower(date):
1347 return parsedate(date, extendeddateformats)[0]
1356 return parsedate(date, extendeddateformats)[0]
1348
1357
1349 def upper(date):
1358 def upper(date):
1350 d = dict(mb="12", HI="23", M="59", S="59")
1359 d = dict(mb="12", HI="23", M="59", S="59")
1351 for days in "31 30 29".split():
1360 for days in "31 30 29".split():
1352 try:
1361 try:
1353 d["d"] = days
1362 d["d"] = days
1354 return parsedate(date, extendeddateformats, d)[0]
1363 return parsedate(date, extendeddateformats, d)[0]
1355 except:
1364 except:
1356 pass
1365 pass
1357 d["d"] = "28"
1366 d["d"] = "28"
1358 return parsedate(date, extendeddateformats, d)[0]
1367 return parsedate(date, extendeddateformats, d)[0]
1359
1368
1360 if date[0] == "<":
1369 if date[0] == "<":
1361 when = upper(date[1:])
1370 when = upper(date[1:])
1362 return lambda x: x <= when
1371 return lambda x: x <= when
1363 elif date[0] == ">":
1372 elif date[0] == ">":
1364 when = lower(date[1:])
1373 when = lower(date[1:])
1365 return lambda x: x >= when
1374 return lambda x: x >= when
1366 elif date[0] == "-":
1375 elif date[0] == "-":
1367 try:
1376 try:
1368 days = int(date[1:])
1377 days = int(date[1:])
1369 except ValueError:
1378 except ValueError:
1370 raise Abort(_("invalid day spec: %s") % date[1:])
1379 raise Abort(_("invalid day spec: %s") % date[1:])
1371 when = makedate()[0] - days * 3600 * 24
1380 when = makedate()[0] - days * 3600 * 24
1372 return lambda x: x >= when
1381 return lambda x: x >= when
1373 elif " to " in date:
1382 elif " to " in date:
1374 a, b = date.split(" to ")
1383 a, b = date.split(" to ")
1375 start, stop = lower(a), upper(b)
1384 start, stop = lower(a), upper(b)
1376 return lambda x: x >= start and x <= stop
1385 return lambda x: x >= start and x <= stop
1377 else:
1386 else:
1378 start, stop = lower(date), upper(date)
1387 start, stop = lower(date), upper(date)
1379 return lambda x: x >= start and x <= stop
1388 return lambda x: x >= start and x <= stop
1380
1389
1381 def shortuser(user):
1390 def shortuser(user):
1382 """Return a short representation of a user name or email address."""
1391 """Return a short representation of a user name or email address."""
1383 f = user.find('@')
1392 f = user.find('@')
1384 if f >= 0:
1393 if f >= 0:
1385 user = user[:f]
1394 user = user[:f]
1386 f = user.find('<')
1395 f = user.find('<')
1387 if f >= 0:
1396 if f >= 0:
1388 user = user[f+1:]
1397 user = user[f+1:]
1389 f = user.find(' ')
1398 f = user.find(' ')
1390 if f >= 0:
1399 if f >= 0:
1391 user = user[:f]
1400 user = user[:f]
1392 f = user.find('.')
1401 f = user.find('.')
1393 if f >= 0:
1402 if f >= 0:
1394 user = user[:f]
1403 user = user[:f]
1395 return user
1404 return user
1396
1405
1397 def ellipsis(text, maxlength=400):
1406 def ellipsis(text, maxlength=400):
1398 """Trim string to at most maxlength (default: 400) characters."""
1407 """Trim string to at most maxlength (default: 400) characters."""
1399 if len(text) <= maxlength:
1408 if len(text) <= maxlength:
1400 return text
1409 return text
1401 else:
1410 else:
1402 return "%s..." % (text[:maxlength-3])
1411 return "%s..." % (text[:maxlength-3])
1403
1412
1404 def walkrepos(path):
1413 def walkrepos(path):
1405 '''yield every hg repository under path, recursively.'''
1414 '''yield every hg repository under path, recursively.'''
1406 def errhandler(err):
1415 def errhandler(err):
1407 if err.filename == path:
1416 if err.filename == path:
1408 raise err
1417 raise err
1409
1418
1410 for root, dirs, files in os.walk(path, onerror=errhandler):
1419 for root, dirs, files in os.walk(path, onerror=errhandler):
1411 for d in dirs:
1420 for d in dirs:
1412 if d == '.hg':
1421 if d == '.hg':
1413 yield root
1422 yield root
1414 dirs[:] = []
1423 dirs[:] = []
1415 break
1424 break
1416
1425
1417 _rcpath = None
1426 _rcpath = None
1418
1427
1419 def os_rcpath():
1428 def os_rcpath():
1420 '''return default os-specific hgrc search path'''
1429 '''return default os-specific hgrc search path'''
1421 path = system_rcpath()
1430 path = system_rcpath()
1422 path.extend(user_rcpath())
1431 path.extend(user_rcpath())
1423 path = [os.path.normpath(f) for f in path]
1432 path = [os.path.normpath(f) for f in path]
1424 return path
1433 return path
1425
1434
1426 def rcpath():
1435 def rcpath():
1427 '''return hgrc search path. if env var HGRCPATH is set, use it.
1436 '''return hgrc search path. if env var HGRCPATH is set, use it.
1428 for each item in path, if directory, use files ending in .rc,
1437 for each item in path, if directory, use files ending in .rc,
1429 else use item.
1438 else use item.
1430 make HGRCPATH empty to only look in .hg/hgrc of current repo.
1439 make HGRCPATH empty to only look in .hg/hgrc of current repo.
1431 if no HGRCPATH, use default os-specific path.'''
1440 if no HGRCPATH, use default os-specific path.'''
1432 global _rcpath
1441 global _rcpath
1433 if _rcpath is None:
1442 if _rcpath is None:
1434 if 'HGRCPATH' in os.environ:
1443 if 'HGRCPATH' in os.environ:
1435 _rcpath = []
1444 _rcpath = []
1436 for p in os.environ['HGRCPATH'].split(os.pathsep):
1445 for p in os.environ['HGRCPATH'].split(os.pathsep):
1437 if not p: continue
1446 if not p: continue
1438 if os.path.isdir(p):
1447 if os.path.isdir(p):
1439 for f in os.listdir(p):
1448 for f in os.listdir(p):
1440 if f.endswith('.rc'):
1449 if f.endswith('.rc'):
1441 _rcpath.append(os.path.join(p, f))
1450 _rcpath.append(os.path.join(p, f))
1442 else:
1451 else:
1443 _rcpath.append(p)
1452 _rcpath.append(p)
1444 else:
1453 else:
1445 _rcpath = os_rcpath()
1454 _rcpath = os_rcpath()
1446 return _rcpath
1455 return _rcpath
1447
1456
1448 def bytecount(nbytes):
1457 def bytecount(nbytes):
1449 '''return byte count formatted as readable string, with units'''
1458 '''return byte count formatted as readable string, with units'''
1450
1459
1451 units = (
1460 units = (
1452 (100, 1<<30, _('%.0f GB')),
1461 (100, 1<<30, _('%.0f GB')),
1453 (10, 1<<30, _('%.1f GB')),
1462 (10, 1<<30, _('%.1f GB')),
1454 (1, 1<<30, _('%.2f GB')),
1463 (1, 1<<30, _('%.2f GB')),
1455 (100, 1<<20, _('%.0f MB')),
1464 (100, 1<<20, _('%.0f MB')),
1456 (10, 1<<20, _('%.1f MB')),
1465 (10, 1<<20, _('%.1f MB')),
1457 (1, 1<<20, _('%.2f MB')),
1466 (1, 1<<20, _('%.2f MB')),
1458 (100, 1<<10, _('%.0f KB')),
1467 (100, 1<<10, _('%.0f KB')),
1459 (10, 1<<10, _('%.1f KB')),
1468 (10, 1<<10, _('%.1f KB')),
1460 (1, 1<<10, _('%.2f KB')),
1469 (1, 1<<10, _('%.2f KB')),
1461 (1, 1, _('%.0f bytes')),
1470 (1, 1, _('%.0f bytes')),
1462 )
1471 )
1463
1472
1464 for multiplier, divisor, format in units:
1473 for multiplier, divisor, format in units:
1465 if nbytes >= divisor * multiplier:
1474 if nbytes >= divisor * multiplier:
1466 return format % (nbytes / float(divisor))
1475 return format % (nbytes / float(divisor))
1467 return units[-1][2] % nbytes
1476 return units[-1][2] % nbytes
1468
1477
1469 def drop_scheme(scheme, path):
1478 def drop_scheme(scheme, path):
1470 sc = scheme + ':'
1479 sc = scheme + ':'
1471 if path.startswith(sc):
1480 if path.startswith(sc):
1472 path = path[len(sc):]
1481 path = path[len(sc):]
1473 if path.startswith('//'):
1482 if path.startswith('//'):
1474 path = path[2:]
1483 path = path[2:]
1475 return path
1484 return path
General Comments 0
You need to be logged in to leave comments. Login now