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