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