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