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