##// END OF EJS Templates
util._matcher: update comments...
Alexis S. L. Carvalho -
r4185:51ee2868 default
parent child Browse files
Show More
@@ -1,1369 +1,1373 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=[], head='', src=None):
381 def matcher(canonroot, cwd='', names=['.'], inc=[], exc=[], head='', src=None):
382 return _matcher(canonroot, cwd, names, inc, exc, head, 'glob', src)
382 return _matcher(canonroot, cwd, names, inc, exc, head, 'glob', src)
383
383
384 def cmdmatcher(canonroot, cwd='', names=['.'], inc=[], exc=[], head='',
384 def cmdmatcher(canonroot, cwd='', names=['.'], inc=[], exc=[], head='',
385 src=None, globbed=False):
385 src=None, globbed=False):
386 if not globbed:
386 if not globbed:
387 names = expand_glob(names)
387 names = expand_glob(names)
388 return _matcher(canonroot, cwd, names, inc, exc, head, 'relpath', src)
388 return _matcher(canonroot, cwd, names, inc, exc, head, 'relpath', src)
389
389
390 def _matcher(canonroot, cwd, names, inc, exc, head, dflt_pat, src):
390 def _matcher(canonroot, cwd, names, inc, exc, head, dflt_pat, src):
391 """build a function to match a set of file patterns
391 """build a function to match a set of file patterns
392
392
393 arguments:
393 arguments:
394 canonroot - the canonical root of the tree you're matching against
394 canonroot - the canonical root of the tree you're matching against
395 cwd - the current working directory, if relevant
395 cwd - the current working directory, if relevant
396 names - patterns to find
396 names - patterns to find
397 inc - patterns to include
397 inc - patterns to include
398 exc - patterns to exclude
398 exc - patterns to exclude
399 head - a regex to prepend to patterns to control whether a match is rooted
399 head - a regex to prepend to patterns to control whether a match is rooted
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)
400
402
401 a pattern is one of:
403 a pattern is one of:
402 'glob:<rooted glob>'
404 'glob:<glob>' - a glob relative to cwd
403 're:<rooted regexp>'
405 're:<regexp>' - a regular expression
404 'path:<rooted path>'
406 'path:<path>' - a path relative to canonroot
405 'relglob:<relative glob>'
407 'relglob:<glob>' - an unrooted glob (*.c matches C files in all dirs)
406 'relpath:<relative path>'
408 'relpath:<path>' - a path relative to cwd
407 'relre:<relative regexp>'
409 'relre:<regexp>' - a regexp that doesn't have to match the start of a name
408 '<rooted path or regexp>'
410 '<something>' - one of the cases above, selected by the dflt_pat argument
409
411
410 returns:
412 returns:
411 a 3-tuple containing
413 a 3-tuple containing
412 - list of explicit non-pattern names passed in
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
416 includes the initial part of glob: patterns that has no glob characters
413 - a bool match(filename) function
417 - a bool match(filename) function
414 - a bool indicating if any patterns were passed in
418 - a bool indicating if any patterns were passed in
415
419
416 todo:
420 todo:
417 make head regex a rooted bool
421 make head regex a rooted bool
418 """
422 """
419
423
420 def contains_glob(name):
424 def contains_glob(name):
421 for c in name:
425 for c in name:
422 if c in _globchars: return True
426 if c in _globchars: return True
423 return False
427 return False
424
428
425 def regex(kind, name, tail):
429 def regex(kind, name, tail):
426 '''convert a pattern into a regular expression'''
430 '''convert a pattern into a regular expression'''
427 if kind == 're':
431 if kind == 're':
428 return name
432 return name
429 elif kind == 'path':
433 elif kind == 'path':
430 return '^' + re.escape(name) + '(?:/|$)'
434 return '^' + re.escape(name) + '(?:/|$)'
431 elif kind == 'relglob':
435 elif kind == 'relglob':
432 return head + globre(name, '(?:|.*/)', tail)
436 return head + globre(name, '(?:|.*/)', tail)
433 elif kind == 'relpath':
437 elif kind == 'relpath':
434 return head + re.escape(name) + tail
438 return head + re.escape(name) + tail
435 elif kind == 'relre':
439 elif kind == 'relre':
436 if name.startswith('^'):
440 if name.startswith('^'):
437 return name
441 return name
438 return '.*' + name
442 return '.*' + name
439 return head + globre(name, '', tail)
443 return head + globre(name, '', tail)
440
444
441 def matchfn(pats, tail):
445 def matchfn(pats, tail):
442 """build a matching function from a set of patterns"""
446 """build a matching function from a set of patterns"""
443 if not pats:
447 if not pats:
444 return
448 return
445 matches = []
449 matches = []
446 for k, p in pats:
450 for k, p in pats:
447 try:
451 try:
448 pat = '(?:%s)' % regex(k, p, tail)
452 pat = '(?:%s)' % regex(k, p, tail)
449 matches.append(re.compile(pat).match)
453 matches.append(re.compile(pat).match)
450 except re.error:
454 except re.error:
451 if src: raise Abort("%s: invalid pattern (%s): %s" % (src, k, p))
455 if src: raise Abort("%s: invalid pattern (%s): %s" % (src, k, p))
452 else: raise Abort("invalid pattern (%s): %s" % (k, p))
456 else: raise Abort("invalid pattern (%s): %s" % (k, p))
453
457
454 def buildfn(text):
458 def buildfn(text):
455 for m in matches:
459 for m in matches:
456 r = m(text)
460 r = m(text)
457 if r:
461 if r:
458 return r
462 return r
459
463
460 return buildfn
464 return buildfn
461
465
462 def globprefix(pat):
466 def globprefix(pat):
463 '''return the non-glob prefix of a path, e.g. foo/* -> foo'''
467 '''return the non-glob prefix of a path, e.g. foo/* -> foo'''
464 root = []
468 root = []
465 for p in pat.split('/'):
469 for p in pat.split('/'):
466 if contains_glob(p): break
470 if contains_glob(p): break
467 root.append(p)
471 root.append(p)
468 return '/'.join(root)
472 return '/'.join(root)
469
473
470 pats = []
474 pats = []
471 files = []
475 files = []
472 roots = []
476 roots = []
473 for kind, name in [patkind(p, dflt_pat) for p in names]:
477 for kind, name in [patkind(p, dflt_pat) for p in names]:
474 if kind in ('glob', 'relpath'):
478 if kind in ('glob', 'relpath'):
475 name = canonpath(canonroot, cwd, name)
479 name = canonpath(canonroot, cwd, name)
476 if name == '':
480 if name == '':
477 kind, name = 'glob', '**'
481 kind, name = 'glob', '**'
478 if kind in ('glob', 'path', 're'):
482 if kind in ('glob', 'path', 're'):
479 pats.append((kind, name))
483 pats.append((kind, name))
480 if kind == 'glob':
484 if kind == 'glob':
481 root = globprefix(name)
485 root = globprefix(name)
482 if root: roots.append(root)
486 if root: roots.append(root)
483 elif kind == 'relpath':
487 elif kind == 'relpath':
484 files.append((kind, name))
488 files.append((kind, name))
485 roots.append(name)
489 roots.append(name)
486
490
487 patmatch = matchfn(pats, '$') or always
491 patmatch = matchfn(pats, '$') or always
488 filematch = matchfn(files, '(?:/|$)') or always
492 filematch = matchfn(files, '(?:/|$)') or always
489 incmatch = always
493 incmatch = always
490 if inc:
494 if inc:
491 inckinds = [patkind(canonpath(canonroot, cwd, i)) for i in inc]
495 inckinds = [patkind(canonpath(canonroot, cwd, i)) for i in inc]
492 incmatch = matchfn(inckinds, '(?:/|$)')
496 incmatch = matchfn(inckinds, '(?:/|$)')
493 excmatch = lambda fn: False
497 excmatch = lambda fn: False
494 if exc:
498 if exc:
495 exckinds = [patkind(canonpath(canonroot, cwd, x)) for x in exc]
499 exckinds = [patkind(canonpath(canonroot, cwd, x)) for x in exc]
496 excmatch = matchfn(exckinds, '(?:/|$)')
500 excmatch = matchfn(exckinds, '(?:/|$)')
497
501
498 return (roots,
502 return (roots,
499 lambda fn: (incmatch(fn) and not excmatch(fn) and
503 lambda fn: (incmatch(fn) and not excmatch(fn) and
500 (fn.endswith('/') or
504 (fn.endswith('/') or
501 (not pats and not files) or
505 (not pats and not files) or
502 (pats and patmatch(fn)) or
506 (pats and patmatch(fn)) or
503 (files and filematch(fn)))),
507 (files and filematch(fn)))),
504 (inc or exc or (pats and pats != [('glob', '**')])) and True)
508 (inc or exc or (pats and pats != [('glob', '**')])) and True)
505
509
506 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None):
510 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None):
507 '''enhanced shell command execution.
511 '''enhanced shell command execution.
508 run with environment maybe modified, maybe in different dir.
512 run with environment maybe modified, maybe in different dir.
509
513
510 if command fails and onerr is None, return status. if ui object,
514 if command fails and onerr is None, return status. if ui object,
511 print error message and return status, else raise onerr object as
515 print error message and return status, else raise onerr object as
512 exception.'''
516 exception.'''
513 def py2shell(val):
517 def py2shell(val):
514 'convert python object into string that is useful to shell'
518 'convert python object into string that is useful to shell'
515 if val in (None, False):
519 if val in (None, False):
516 return '0'
520 return '0'
517 if val == True:
521 if val == True:
518 return '1'
522 return '1'
519 return str(val)
523 return str(val)
520 oldenv = {}
524 oldenv = {}
521 for k in environ:
525 for k in environ:
522 oldenv[k] = os.environ.get(k)
526 oldenv[k] = os.environ.get(k)
523 if cwd is not None:
527 if cwd is not None:
524 oldcwd = os.getcwd()
528 oldcwd = os.getcwd()
525 origcmd = cmd
529 origcmd = cmd
526 if os.name == 'nt':
530 if os.name == 'nt':
527 cmd = '"%s"' % cmd
531 cmd = '"%s"' % cmd
528 try:
532 try:
529 for k, v in environ.iteritems():
533 for k, v in environ.iteritems():
530 os.environ[k] = py2shell(v)
534 os.environ[k] = py2shell(v)
531 if cwd is not None and oldcwd != cwd:
535 if cwd is not None and oldcwd != cwd:
532 os.chdir(cwd)
536 os.chdir(cwd)
533 rc = os.system(cmd)
537 rc = os.system(cmd)
534 if rc and onerr:
538 if rc and onerr:
535 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
539 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
536 explain_exit(rc)[0])
540 explain_exit(rc)[0])
537 if errprefix:
541 if errprefix:
538 errmsg = '%s: %s' % (errprefix, errmsg)
542 errmsg = '%s: %s' % (errprefix, errmsg)
539 try:
543 try:
540 onerr.warn(errmsg + '\n')
544 onerr.warn(errmsg + '\n')
541 except AttributeError:
545 except AttributeError:
542 raise onerr(errmsg)
546 raise onerr(errmsg)
543 return rc
547 return rc
544 finally:
548 finally:
545 for k, v in oldenv.iteritems():
549 for k, v in oldenv.iteritems():
546 if v is None:
550 if v is None:
547 del os.environ[k]
551 del os.environ[k]
548 else:
552 else:
549 os.environ[k] = v
553 os.environ[k] = v
550 if cwd is not None and oldcwd != cwd:
554 if cwd is not None and oldcwd != cwd:
551 os.chdir(oldcwd)
555 os.chdir(oldcwd)
552
556
553 def rename(src, dst):
557 def rename(src, dst):
554 """forcibly rename a file"""
558 """forcibly rename a file"""
555 try:
559 try:
556 os.rename(src, dst)
560 os.rename(src, dst)
557 except OSError, err:
561 except OSError, err:
558 # on windows, rename to existing file is not allowed, so we
562 # on windows, rename to existing file is not allowed, so we
559 # must delete destination first. but if file is open, unlink
563 # must delete destination first. but if file is open, unlink
560 # schedules it for delete but does not delete it. rename
564 # schedules it for delete but does not delete it. rename
561 # happens immediately even for open files, so we create
565 # happens immediately even for open files, so we create
562 # temporary file, delete it, rename destination to that name,
566 # temporary file, delete it, rename destination to that name,
563 # then delete that. then rename is safe to do.
567 # then delete that. then rename is safe to do.
564 fd, temp = tempfile.mkstemp(dir=os.path.dirname(dst) or '.')
568 fd, temp = tempfile.mkstemp(dir=os.path.dirname(dst) or '.')
565 os.close(fd)
569 os.close(fd)
566 os.unlink(temp)
570 os.unlink(temp)
567 os.rename(dst, temp)
571 os.rename(dst, temp)
568 os.unlink(temp)
572 os.unlink(temp)
569 os.rename(src, dst)
573 os.rename(src, dst)
570
574
571 def unlink(f):
575 def unlink(f):
572 """unlink and remove the directory if it is empty"""
576 """unlink and remove the directory if it is empty"""
573 os.unlink(f)
577 os.unlink(f)
574 # try removing directories that might now be empty
578 # try removing directories that might now be empty
575 try:
579 try:
576 os.removedirs(os.path.dirname(f))
580 os.removedirs(os.path.dirname(f))
577 except OSError:
581 except OSError:
578 pass
582 pass
579
583
580 def copyfile(src, dest):
584 def copyfile(src, dest):
581 "copy a file, preserving mode"
585 "copy a file, preserving mode"
582 try:
586 try:
583 shutil.copyfile(src, dest)
587 shutil.copyfile(src, dest)
584 shutil.copymode(src, dest)
588 shutil.copymode(src, dest)
585 except shutil.Error, inst:
589 except shutil.Error, inst:
586 raise Abort(str(inst))
590 raise Abort(str(inst))
587
591
588 def copyfiles(src, dst, hardlink=None):
592 def copyfiles(src, dst, hardlink=None):
589 """Copy a directory tree using hardlinks if possible"""
593 """Copy a directory tree using hardlinks if possible"""
590
594
591 if hardlink is None:
595 if hardlink is None:
592 hardlink = (os.stat(src).st_dev ==
596 hardlink = (os.stat(src).st_dev ==
593 os.stat(os.path.dirname(dst)).st_dev)
597 os.stat(os.path.dirname(dst)).st_dev)
594
598
595 if os.path.isdir(src):
599 if os.path.isdir(src):
596 os.mkdir(dst)
600 os.mkdir(dst)
597 for name in os.listdir(src):
601 for name in os.listdir(src):
598 srcname = os.path.join(src, name)
602 srcname = os.path.join(src, name)
599 dstname = os.path.join(dst, name)
603 dstname = os.path.join(dst, name)
600 copyfiles(srcname, dstname, hardlink)
604 copyfiles(srcname, dstname, hardlink)
601 else:
605 else:
602 if hardlink:
606 if hardlink:
603 try:
607 try:
604 os_link(src, dst)
608 os_link(src, dst)
605 except (IOError, OSError):
609 except (IOError, OSError):
606 hardlink = False
610 hardlink = False
607 shutil.copy(src, dst)
611 shutil.copy(src, dst)
608 else:
612 else:
609 shutil.copy(src, dst)
613 shutil.copy(src, dst)
610
614
611 def audit_path(path):
615 def audit_path(path):
612 """Abort if path contains dangerous components"""
616 """Abort if path contains dangerous components"""
613 parts = os.path.normcase(path).split(os.sep)
617 parts = os.path.normcase(path).split(os.sep)
614 if (os.path.splitdrive(path)[0] or parts[0] in ('.hg', '')
618 if (os.path.splitdrive(path)[0] or parts[0] in ('.hg', '')
615 or os.pardir in parts):
619 or os.pardir in parts):
616 raise Abort(_("path contains illegal component: %s\n") % path)
620 raise Abort(_("path contains illegal component: %s\n") % path)
617
621
618 def _makelock_file(info, pathname):
622 def _makelock_file(info, pathname):
619 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
623 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
620 os.write(ld, info)
624 os.write(ld, info)
621 os.close(ld)
625 os.close(ld)
622
626
623 def _readlock_file(pathname):
627 def _readlock_file(pathname):
624 return posixfile(pathname).read()
628 return posixfile(pathname).read()
625
629
626 def nlinks(pathname):
630 def nlinks(pathname):
627 """Return number of hardlinks for the given file."""
631 """Return number of hardlinks for the given file."""
628 return os.lstat(pathname).st_nlink
632 return os.lstat(pathname).st_nlink
629
633
630 if hasattr(os, 'link'):
634 if hasattr(os, 'link'):
631 os_link = os.link
635 os_link = os.link
632 else:
636 else:
633 def os_link(src, dst):
637 def os_link(src, dst):
634 raise OSError(0, _("Hardlinks not supported"))
638 raise OSError(0, _("Hardlinks not supported"))
635
639
636 def fstat(fp):
640 def fstat(fp):
637 '''stat file object that may not have fileno method.'''
641 '''stat file object that may not have fileno method.'''
638 try:
642 try:
639 return os.fstat(fp.fileno())
643 return os.fstat(fp.fileno())
640 except AttributeError:
644 except AttributeError:
641 return os.stat(fp.name)
645 return os.stat(fp.name)
642
646
643 posixfile = file
647 posixfile = file
644
648
645 def is_win_9x():
649 def is_win_9x():
646 '''return true if run on windows 95, 98 or me.'''
650 '''return true if run on windows 95, 98 or me.'''
647 try:
651 try:
648 return sys.getwindowsversion()[3] == 1
652 return sys.getwindowsversion()[3] == 1
649 except AttributeError:
653 except AttributeError:
650 return os.name == 'nt' and 'command' in os.environ.get('comspec', '')
654 return os.name == 'nt' and 'command' in os.environ.get('comspec', '')
651
655
652 getuser_fallback = None
656 getuser_fallback = None
653
657
654 def getuser():
658 def getuser():
655 '''return name of current user'''
659 '''return name of current user'''
656 try:
660 try:
657 return getpass.getuser()
661 return getpass.getuser()
658 except ImportError:
662 except ImportError:
659 # import of pwd will fail on windows - try fallback
663 # import of pwd will fail on windows - try fallback
660 if getuser_fallback:
664 if getuser_fallback:
661 return getuser_fallback()
665 return getuser_fallback()
662 # raised if win32api not available
666 # raised if win32api not available
663 raise Abort(_('user name not available - set USERNAME '
667 raise Abort(_('user name not available - set USERNAME '
664 'environment variable'))
668 'environment variable'))
665
669
666 def username(uid=None):
670 def username(uid=None):
667 """Return the name of the user with the given uid.
671 """Return the name of the user with the given uid.
668
672
669 If uid is None, return the name of the current user."""
673 If uid is None, return the name of the current user."""
670 try:
674 try:
671 import pwd
675 import pwd
672 if uid is None:
676 if uid is None:
673 uid = os.getuid()
677 uid = os.getuid()
674 try:
678 try:
675 return pwd.getpwuid(uid)[0]
679 return pwd.getpwuid(uid)[0]
676 except KeyError:
680 except KeyError:
677 return str(uid)
681 return str(uid)
678 except ImportError:
682 except ImportError:
679 return None
683 return None
680
684
681 def groupname(gid=None):
685 def groupname(gid=None):
682 """Return the name of the group with the given gid.
686 """Return the name of the group with the given gid.
683
687
684 If gid is None, return the name of the current group."""
688 If gid is None, return the name of the current group."""
685 try:
689 try:
686 import grp
690 import grp
687 if gid is None:
691 if gid is None:
688 gid = os.getgid()
692 gid = os.getgid()
689 try:
693 try:
690 return grp.getgrgid(gid)[0]
694 return grp.getgrgid(gid)[0]
691 except KeyError:
695 except KeyError:
692 return str(gid)
696 return str(gid)
693 except ImportError:
697 except ImportError:
694 return None
698 return None
695
699
696 # File system features
700 # File system features
697
701
698 def checkfolding(path):
702 def checkfolding(path):
699 """
703 """
700 Check whether the given path is on a case-sensitive filesystem
704 Check whether the given path is on a case-sensitive filesystem
701
705
702 Requires a path (like /foo/.hg) ending with a foldable final
706 Requires a path (like /foo/.hg) ending with a foldable final
703 directory component.
707 directory component.
704 """
708 """
705 s1 = os.stat(path)
709 s1 = os.stat(path)
706 d, b = os.path.split(path)
710 d, b = os.path.split(path)
707 p2 = os.path.join(d, b.upper())
711 p2 = os.path.join(d, b.upper())
708 if path == p2:
712 if path == p2:
709 p2 = os.path.join(d, b.lower())
713 p2 = os.path.join(d, b.lower())
710 try:
714 try:
711 s2 = os.stat(p2)
715 s2 = os.stat(p2)
712 if s2 == s1:
716 if s2 == s1:
713 return False
717 return False
714 return True
718 return True
715 except:
719 except:
716 return True
720 return True
717
721
718 # Platform specific variants
722 # Platform specific variants
719 if os.name == 'nt':
723 if os.name == 'nt':
720 demandload(globals(), "msvcrt")
724 demandload(globals(), "msvcrt")
721 nulldev = 'NUL:'
725 nulldev = 'NUL:'
722
726
723 class winstdout:
727 class winstdout:
724 '''stdout on windows misbehaves if sent through a pipe'''
728 '''stdout on windows misbehaves if sent through a pipe'''
725
729
726 def __init__(self, fp):
730 def __init__(self, fp):
727 self.fp = fp
731 self.fp = fp
728
732
729 def __getattr__(self, key):
733 def __getattr__(self, key):
730 return getattr(self.fp, key)
734 return getattr(self.fp, key)
731
735
732 def close(self):
736 def close(self):
733 try:
737 try:
734 self.fp.close()
738 self.fp.close()
735 except: pass
739 except: pass
736
740
737 def write(self, s):
741 def write(self, s):
738 try:
742 try:
739 return self.fp.write(s)
743 return self.fp.write(s)
740 except IOError, inst:
744 except IOError, inst:
741 if inst.errno != 0: raise
745 if inst.errno != 0: raise
742 self.close()
746 self.close()
743 raise IOError(errno.EPIPE, 'Broken pipe')
747 raise IOError(errno.EPIPE, 'Broken pipe')
744
748
745 def flush(self):
749 def flush(self):
746 try:
750 try:
747 return self.fp.flush()
751 return self.fp.flush()
748 except IOError, inst:
752 except IOError, inst:
749 if inst.errno != errno.EINVAL: raise
753 if inst.errno != errno.EINVAL: raise
750 self.close()
754 self.close()
751 raise IOError(errno.EPIPE, 'Broken pipe')
755 raise IOError(errno.EPIPE, 'Broken pipe')
752
756
753 sys.stdout = winstdout(sys.stdout)
757 sys.stdout = winstdout(sys.stdout)
754
758
755 def system_rcpath():
759 def system_rcpath():
756 try:
760 try:
757 return system_rcpath_win32()
761 return system_rcpath_win32()
758 except:
762 except:
759 return [r'c:\mercurial\mercurial.ini']
763 return [r'c:\mercurial\mercurial.ini']
760
764
761 def os_rcpath():
765 def os_rcpath():
762 '''return default os-specific hgrc search path'''
766 '''return default os-specific hgrc search path'''
763 path = system_rcpath()
767 path = system_rcpath()
764 path.append(user_rcpath())
768 path.append(user_rcpath())
765 userprofile = os.environ.get('USERPROFILE')
769 userprofile = os.environ.get('USERPROFILE')
766 if userprofile:
770 if userprofile:
767 path.append(os.path.join(userprofile, 'mercurial.ini'))
771 path.append(os.path.join(userprofile, 'mercurial.ini'))
768 return path
772 return path
769
773
770 def user_rcpath():
774 def user_rcpath():
771 '''return os-specific hgrc search path to the user dir'''
775 '''return os-specific hgrc search path to the user dir'''
772 return os.path.join(os.path.expanduser('~'), 'mercurial.ini')
776 return os.path.join(os.path.expanduser('~'), 'mercurial.ini')
773
777
774 def parse_patch_output(output_line):
778 def parse_patch_output(output_line):
775 """parses the output produced by patch and returns the file name"""
779 """parses the output produced by patch and returns the file name"""
776 pf = output_line[14:]
780 pf = output_line[14:]
777 if pf[0] == '`':
781 if pf[0] == '`':
778 pf = pf[1:-1] # Remove the quotes
782 pf = pf[1:-1] # Remove the quotes
779 return pf
783 return pf
780
784
781 def testpid(pid):
785 def testpid(pid):
782 '''return False if pid dead, True if running or not known'''
786 '''return False if pid dead, True if running or not known'''
783 return True
787 return True
784
788
785 def is_exec(f, last):
789 def is_exec(f, last):
786 return last
790 return last
787
791
788 def set_exec(f, mode):
792 def set_exec(f, mode):
789 pass
793 pass
790
794
791 def set_binary(fd):
795 def set_binary(fd):
792 msvcrt.setmode(fd.fileno(), os.O_BINARY)
796 msvcrt.setmode(fd.fileno(), os.O_BINARY)
793
797
794 def pconvert(path):
798 def pconvert(path):
795 return path.replace("\\", "/")
799 return path.replace("\\", "/")
796
800
797 def localpath(path):
801 def localpath(path):
798 return path.replace('/', '\\')
802 return path.replace('/', '\\')
799
803
800 def normpath(path):
804 def normpath(path):
801 return pconvert(os.path.normpath(path))
805 return pconvert(os.path.normpath(path))
802
806
803 makelock = _makelock_file
807 makelock = _makelock_file
804 readlock = _readlock_file
808 readlock = _readlock_file
805
809
806 def samestat(s1, s2):
810 def samestat(s1, s2):
807 return False
811 return False
808
812
809 # A sequence of backslashes is special iff it precedes a double quote:
813 # A sequence of backslashes is special iff it precedes a double quote:
810 # - if there's an even number of backslashes, the double quote is not
814 # - if there's an even number of backslashes, the double quote is not
811 # quoted (i.e. it ends the quoted region)
815 # quoted (i.e. it ends the quoted region)
812 # - if there's an odd number of backslashes, the double quote is quoted
816 # - if there's an odd number of backslashes, the double quote is quoted
813 # - in both cases, every pair of backslashes is unquoted into a single
817 # - in both cases, every pair of backslashes is unquoted into a single
814 # backslash
818 # backslash
815 # (See http://msdn2.microsoft.com/en-us/library/a1y7w461.aspx )
819 # (See http://msdn2.microsoft.com/en-us/library/a1y7w461.aspx )
816 # So, to quote a string, we must surround it in double quotes, double
820 # So, to quote a string, we must surround it in double quotes, double
817 # the number of backslashes that preceed double quotes and add another
821 # the number of backslashes that preceed double quotes and add another
818 # backslash before every double quote (being careful with the double
822 # backslash before every double quote (being careful with the double
819 # quote we've appended to the end)
823 # quote we've appended to the end)
820 _quotere = None
824 _quotere = None
821 def shellquote(s):
825 def shellquote(s):
822 global _quotere
826 global _quotere
823 if _quotere is None:
827 if _quotere is None:
824 _quotere = re.compile(r'(\\*)("|\\$)')
828 _quotere = re.compile(r'(\\*)("|\\$)')
825 return '"%s"' % _quotere.sub(r'\1\1\\\2', s)
829 return '"%s"' % _quotere.sub(r'\1\1\\\2', s)
826
830
827 def explain_exit(code):
831 def explain_exit(code):
828 return _("exited with status %d") % code, code
832 return _("exited with status %d") % code, code
829
833
830 # if you change this stub into a real check, please try to implement the
834 # if you change this stub into a real check, please try to implement the
831 # username and groupname functions above, too.
835 # username and groupname functions above, too.
832 def isowner(fp, st=None):
836 def isowner(fp, st=None):
833 return True
837 return True
834
838
835 try:
839 try:
836 # override functions with win32 versions if possible
840 # override functions with win32 versions if possible
837 from util_win32 import *
841 from util_win32 import *
838 if not is_win_9x():
842 if not is_win_9x():
839 posixfile = posixfile_nt
843 posixfile = posixfile_nt
840 except ImportError:
844 except ImportError:
841 pass
845 pass
842
846
843 else:
847 else:
844 nulldev = '/dev/null'
848 nulldev = '/dev/null'
845
849
846 def rcfiles(path):
850 def rcfiles(path):
847 rcs = [os.path.join(path, 'hgrc')]
851 rcs = [os.path.join(path, 'hgrc')]
848 rcdir = os.path.join(path, 'hgrc.d')
852 rcdir = os.path.join(path, 'hgrc.d')
849 try:
853 try:
850 rcs.extend([os.path.join(rcdir, f) for f in os.listdir(rcdir)
854 rcs.extend([os.path.join(rcdir, f) for f in os.listdir(rcdir)
851 if f.endswith(".rc")])
855 if f.endswith(".rc")])
852 except OSError:
856 except OSError:
853 pass
857 pass
854 return rcs
858 return rcs
855
859
856 def os_rcpath():
860 def os_rcpath():
857 '''return default os-specific hgrc search path'''
861 '''return default os-specific hgrc search path'''
858 path = []
862 path = []
859 # old mod_python does not set sys.argv
863 # old mod_python does not set sys.argv
860 if len(getattr(sys, 'argv', [])) > 0:
864 if len(getattr(sys, 'argv', [])) > 0:
861 path.extend(rcfiles(os.path.dirname(sys.argv[0]) +
865 path.extend(rcfiles(os.path.dirname(sys.argv[0]) +
862 '/../etc/mercurial'))
866 '/../etc/mercurial'))
863 path.extend(rcfiles('/etc/mercurial'))
867 path.extend(rcfiles('/etc/mercurial'))
864 path.append(os.path.expanduser('~/.hgrc'))
868 path.append(os.path.expanduser('~/.hgrc'))
865 path = [os.path.normpath(f) for f in path]
869 path = [os.path.normpath(f) for f in path]
866 return path
870 return path
867
871
868 def parse_patch_output(output_line):
872 def parse_patch_output(output_line):
869 """parses the output produced by patch and returns the file name"""
873 """parses the output produced by patch and returns the file name"""
870 pf = output_line[14:]
874 pf = output_line[14:]
871 if pf.startswith("'") and pf.endswith("'") and " " in pf:
875 if pf.startswith("'") and pf.endswith("'") and " " in pf:
872 pf = pf[1:-1] # Remove the quotes
876 pf = pf[1:-1] # Remove the quotes
873 return pf
877 return pf
874
878
875 def is_exec(f, last):
879 def is_exec(f, last):
876 """check whether a file is executable"""
880 """check whether a file is executable"""
877 return (os.lstat(f).st_mode & 0100 != 0)
881 return (os.lstat(f).st_mode & 0100 != 0)
878
882
879 def set_exec(f, mode):
883 def set_exec(f, mode):
880 s = os.lstat(f).st_mode
884 s = os.lstat(f).st_mode
881 if (s & 0100 != 0) == mode:
885 if (s & 0100 != 0) == mode:
882 return
886 return
883 if mode:
887 if mode:
884 # Turn on +x for every +r bit when making a file executable
888 # Turn on +x for every +r bit when making a file executable
885 # and obey umask.
889 # and obey umask.
886 umask = os.umask(0)
890 umask = os.umask(0)
887 os.umask(umask)
891 os.umask(umask)
888 os.chmod(f, s | (s & 0444) >> 2 & ~umask)
892 os.chmod(f, s | (s & 0444) >> 2 & ~umask)
889 else:
893 else:
890 os.chmod(f, s & 0666)
894 os.chmod(f, s & 0666)
891
895
892 def set_binary(fd):
896 def set_binary(fd):
893 pass
897 pass
894
898
895 def pconvert(path):
899 def pconvert(path):
896 return path
900 return path
897
901
898 def localpath(path):
902 def localpath(path):
899 return path
903 return path
900
904
901 normpath = os.path.normpath
905 normpath = os.path.normpath
902 samestat = os.path.samestat
906 samestat = os.path.samestat
903
907
904 def makelock(info, pathname):
908 def makelock(info, pathname):
905 try:
909 try:
906 os.symlink(info, pathname)
910 os.symlink(info, pathname)
907 except OSError, why:
911 except OSError, why:
908 if why.errno == errno.EEXIST:
912 if why.errno == errno.EEXIST:
909 raise
913 raise
910 else:
914 else:
911 _makelock_file(info, pathname)
915 _makelock_file(info, pathname)
912
916
913 def readlock(pathname):
917 def readlock(pathname):
914 try:
918 try:
915 return os.readlink(pathname)
919 return os.readlink(pathname)
916 except OSError, why:
920 except OSError, why:
917 if why.errno == errno.EINVAL:
921 if why.errno == errno.EINVAL:
918 return _readlock_file(pathname)
922 return _readlock_file(pathname)
919 else:
923 else:
920 raise
924 raise
921
925
922 def shellquote(s):
926 def shellquote(s):
923 return "'%s'" % s.replace("'", "'\\''")
927 return "'%s'" % s.replace("'", "'\\''")
924
928
925 def testpid(pid):
929 def testpid(pid):
926 '''return False if pid dead, True if running or not sure'''
930 '''return False if pid dead, True if running or not sure'''
927 try:
931 try:
928 os.kill(pid, 0)
932 os.kill(pid, 0)
929 return True
933 return True
930 except OSError, inst:
934 except OSError, inst:
931 return inst.errno != errno.ESRCH
935 return inst.errno != errno.ESRCH
932
936
933 def explain_exit(code):
937 def explain_exit(code):
934 """return a 2-tuple (desc, code) describing a process's status"""
938 """return a 2-tuple (desc, code) describing a process's status"""
935 if os.WIFEXITED(code):
939 if os.WIFEXITED(code):
936 val = os.WEXITSTATUS(code)
940 val = os.WEXITSTATUS(code)
937 return _("exited with status %d") % val, val
941 return _("exited with status %d") % val, val
938 elif os.WIFSIGNALED(code):
942 elif os.WIFSIGNALED(code):
939 val = os.WTERMSIG(code)
943 val = os.WTERMSIG(code)
940 return _("killed by signal %d") % val, val
944 return _("killed by signal %d") % val, val
941 elif os.WIFSTOPPED(code):
945 elif os.WIFSTOPPED(code):
942 val = os.WSTOPSIG(code)
946 val = os.WSTOPSIG(code)
943 return _("stopped by signal %d") % val, val
947 return _("stopped by signal %d") % val, val
944 raise ValueError(_("invalid exit code"))
948 raise ValueError(_("invalid exit code"))
945
949
946 def isowner(fp, st=None):
950 def isowner(fp, st=None):
947 """Return True if the file object f belongs to the current user.
951 """Return True if the file object f belongs to the current user.
948
952
949 The return value of a util.fstat(f) may be passed as the st argument.
953 The return value of a util.fstat(f) may be passed as the st argument.
950 """
954 """
951 if st is None:
955 if st is None:
952 st = fstat(fp)
956 st = fstat(fp)
953 return st.st_uid == os.getuid()
957 return st.st_uid == os.getuid()
954
958
955 def _buildencodefun():
959 def _buildencodefun():
956 e = '_'
960 e = '_'
957 win_reserved = [ord(x) for x in '\\:*?"<>|']
961 win_reserved = [ord(x) for x in '\\:*?"<>|']
958 cmap = dict([ (chr(x), chr(x)) for x in xrange(127) ])
962 cmap = dict([ (chr(x), chr(x)) for x in xrange(127) ])
959 for x in (range(32) + range(126, 256) + win_reserved):
963 for x in (range(32) + range(126, 256) + win_reserved):
960 cmap[chr(x)] = "~%02x" % x
964 cmap[chr(x)] = "~%02x" % x
961 for x in range(ord("A"), ord("Z")+1) + [ord(e)]:
965 for x in range(ord("A"), ord("Z")+1) + [ord(e)]:
962 cmap[chr(x)] = e + chr(x).lower()
966 cmap[chr(x)] = e + chr(x).lower()
963 dmap = {}
967 dmap = {}
964 for k, v in cmap.iteritems():
968 for k, v in cmap.iteritems():
965 dmap[v] = k
969 dmap[v] = k
966 def decode(s):
970 def decode(s):
967 i = 0
971 i = 0
968 while i < len(s):
972 while i < len(s):
969 for l in xrange(1, 4):
973 for l in xrange(1, 4):
970 try:
974 try:
971 yield dmap[s[i:i+l]]
975 yield dmap[s[i:i+l]]
972 i += l
976 i += l
973 break
977 break
974 except KeyError:
978 except KeyError:
975 pass
979 pass
976 else:
980 else:
977 raise KeyError
981 raise KeyError
978 return (lambda s: "".join([cmap[c] for c in s]),
982 return (lambda s: "".join([cmap[c] for c in s]),
979 lambda s: "".join(list(decode(s))))
983 lambda s: "".join(list(decode(s))))
980
984
981 encodefilename, decodefilename = _buildencodefun()
985 encodefilename, decodefilename = _buildencodefun()
982
986
983 def encodedopener(openerfn, fn):
987 def encodedopener(openerfn, fn):
984 def o(path, *args, **kw):
988 def o(path, *args, **kw):
985 return openerfn(fn(path), *args, **kw)
989 return openerfn(fn(path), *args, **kw)
986 return o
990 return o
987
991
988 def opener(base, audit=True):
992 def opener(base, audit=True):
989 """
993 """
990 return a function that opens files relative to base
994 return a function that opens files relative to base
991
995
992 this function is used to hide the details of COW semantics and
996 this function is used to hide the details of COW semantics and
993 remote file access from higher level code.
997 remote file access from higher level code.
994 """
998 """
995 p = base
999 p = base
996 audit_p = audit
1000 audit_p = audit
997
1001
998 def mktempcopy(name):
1002 def mktempcopy(name):
999 d, fn = os.path.split(name)
1003 d, fn = os.path.split(name)
1000 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
1004 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
1001 os.close(fd)
1005 os.close(fd)
1002 ofp = posixfile(temp, "wb")
1006 ofp = posixfile(temp, "wb")
1003 try:
1007 try:
1004 try:
1008 try:
1005 ifp = posixfile(name, "rb")
1009 ifp = posixfile(name, "rb")
1006 except IOError, inst:
1010 except IOError, inst:
1007 if not getattr(inst, 'filename', None):
1011 if not getattr(inst, 'filename', None):
1008 inst.filename = name
1012 inst.filename = name
1009 raise
1013 raise
1010 for chunk in filechunkiter(ifp):
1014 for chunk in filechunkiter(ifp):
1011 ofp.write(chunk)
1015 ofp.write(chunk)
1012 ifp.close()
1016 ifp.close()
1013 ofp.close()
1017 ofp.close()
1014 except:
1018 except:
1015 try: os.unlink(temp)
1019 try: os.unlink(temp)
1016 except: pass
1020 except: pass
1017 raise
1021 raise
1018 st = os.lstat(name)
1022 st = os.lstat(name)
1019 os.chmod(temp, st.st_mode)
1023 os.chmod(temp, st.st_mode)
1020 return temp
1024 return temp
1021
1025
1022 class atomictempfile(posixfile):
1026 class atomictempfile(posixfile):
1023 """the file will only be copied when rename is called"""
1027 """the file will only be copied when rename is called"""
1024 def __init__(self, name, mode):
1028 def __init__(self, name, mode):
1025 self.__name = name
1029 self.__name = name
1026 self.temp = mktempcopy(name)
1030 self.temp = mktempcopy(name)
1027 posixfile.__init__(self, self.temp, mode)
1031 posixfile.__init__(self, self.temp, mode)
1028 def rename(self):
1032 def rename(self):
1029 if not self.closed:
1033 if not self.closed:
1030 posixfile.close(self)
1034 posixfile.close(self)
1031 rename(self.temp, localpath(self.__name))
1035 rename(self.temp, localpath(self.__name))
1032 def __del__(self):
1036 def __del__(self):
1033 if not self.closed:
1037 if not self.closed:
1034 try:
1038 try:
1035 os.unlink(self.temp)
1039 os.unlink(self.temp)
1036 except: pass
1040 except: pass
1037 posixfile.close(self)
1041 posixfile.close(self)
1038
1042
1039 class atomicfile(atomictempfile):
1043 class atomicfile(atomictempfile):
1040 """the file will only be copied on close"""
1044 """the file will only be copied on close"""
1041 def __init__(self, name, mode):
1045 def __init__(self, name, mode):
1042 atomictempfile.__init__(self, name, mode)
1046 atomictempfile.__init__(self, name, mode)
1043 def close(self):
1047 def close(self):
1044 self.rename()
1048 self.rename()
1045 def __del__(self):
1049 def __del__(self):
1046 self.rename()
1050 self.rename()
1047
1051
1048 def o(path, mode="r", text=False, atomic=False, atomictemp=False):
1052 def o(path, mode="r", text=False, atomic=False, atomictemp=False):
1049 if audit_p:
1053 if audit_p:
1050 audit_path(path)
1054 audit_path(path)
1051 f = os.path.join(p, path)
1055 f = os.path.join(p, path)
1052
1056
1053 if not text:
1057 if not text:
1054 mode += "b" # for that other OS
1058 mode += "b" # for that other OS
1055
1059
1056 if mode[0] != "r":
1060 if mode[0] != "r":
1057 try:
1061 try:
1058 nlink = nlinks(f)
1062 nlink = nlinks(f)
1059 except OSError:
1063 except OSError:
1060 d = os.path.dirname(f)
1064 d = os.path.dirname(f)
1061 if not os.path.isdir(d):
1065 if not os.path.isdir(d):
1062 os.makedirs(d)
1066 os.makedirs(d)
1063 else:
1067 else:
1064 if atomic:
1068 if atomic:
1065 return atomicfile(f, mode)
1069 return atomicfile(f, mode)
1066 elif atomictemp:
1070 elif atomictemp:
1067 return atomictempfile(f, mode)
1071 return atomictempfile(f, mode)
1068 if nlink > 1:
1072 if nlink > 1:
1069 rename(mktempcopy(f), f)
1073 rename(mktempcopy(f), f)
1070 return posixfile(f, mode)
1074 return posixfile(f, mode)
1071
1075
1072 return o
1076 return o
1073
1077
1074 class chunkbuffer(object):
1078 class chunkbuffer(object):
1075 """Allow arbitrary sized chunks of data to be efficiently read from an
1079 """Allow arbitrary sized chunks of data to be efficiently read from an
1076 iterator over chunks of arbitrary size."""
1080 iterator over chunks of arbitrary size."""
1077
1081
1078 def __init__(self, in_iter, targetsize = 2**16):
1082 def __init__(self, in_iter, targetsize = 2**16):
1079 """in_iter is the iterator that's iterating over the input chunks.
1083 """in_iter is the iterator that's iterating over the input chunks.
1080 targetsize is how big a buffer to try to maintain."""
1084 targetsize is how big a buffer to try to maintain."""
1081 self.in_iter = iter(in_iter)
1085 self.in_iter = iter(in_iter)
1082 self.buf = ''
1086 self.buf = ''
1083 self.targetsize = int(targetsize)
1087 self.targetsize = int(targetsize)
1084 if self.targetsize <= 0:
1088 if self.targetsize <= 0:
1085 raise ValueError(_("targetsize must be greater than 0, was %d") %
1089 raise ValueError(_("targetsize must be greater than 0, was %d") %
1086 targetsize)
1090 targetsize)
1087 self.iterempty = False
1091 self.iterempty = False
1088
1092
1089 def fillbuf(self):
1093 def fillbuf(self):
1090 """Ignore target size; read every chunk from iterator until empty."""
1094 """Ignore target size; read every chunk from iterator until empty."""
1091 if not self.iterempty:
1095 if not self.iterempty:
1092 collector = cStringIO.StringIO()
1096 collector = cStringIO.StringIO()
1093 collector.write(self.buf)
1097 collector.write(self.buf)
1094 for ch in self.in_iter:
1098 for ch in self.in_iter:
1095 collector.write(ch)
1099 collector.write(ch)
1096 self.buf = collector.getvalue()
1100 self.buf = collector.getvalue()
1097 self.iterempty = True
1101 self.iterempty = True
1098
1102
1099 def read(self, l):
1103 def read(self, l):
1100 """Read L bytes of data from the iterator of chunks of data.
1104 """Read L bytes of data from the iterator of chunks of data.
1101 Returns less than L bytes if the iterator runs dry."""
1105 Returns less than L bytes if the iterator runs dry."""
1102 if l > len(self.buf) and not self.iterempty:
1106 if l > len(self.buf) and not self.iterempty:
1103 # Clamp to a multiple of self.targetsize
1107 # Clamp to a multiple of self.targetsize
1104 targetsize = self.targetsize * ((l // self.targetsize) + 1)
1108 targetsize = self.targetsize * ((l // self.targetsize) + 1)
1105 collector = cStringIO.StringIO()
1109 collector = cStringIO.StringIO()
1106 collector.write(self.buf)
1110 collector.write(self.buf)
1107 collected = len(self.buf)
1111 collected = len(self.buf)
1108 for chunk in self.in_iter:
1112 for chunk in self.in_iter:
1109 collector.write(chunk)
1113 collector.write(chunk)
1110 collected += len(chunk)
1114 collected += len(chunk)
1111 if collected >= targetsize:
1115 if collected >= targetsize:
1112 break
1116 break
1113 if collected < targetsize:
1117 if collected < targetsize:
1114 self.iterempty = True
1118 self.iterempty = True
1115 self.buf = collector.getvalue()
1119 self.buf = collector.getvalue()
1116 s, self.buf = self.buf[:l], buffer(self.buf, l)
1120 s, self.buf = self.buf[:l], buffer(self.buf, l)
1117 return s
1121 return s
1118
1122
1119 def filechunkiter(f, size=65536, limit=None):
1123 def filechunkiter(f, size=65536, limit=None):
1120 """Create a generator that produces the data in the file size
1124 """Create a generator that produces the data in the file size
1121 (default 65536) bytes at a time, up to optional limit (default is
1125 (default 65536) bytes at a time, up to optional limit (default is
1122 to read all data). Chunks may be less than size bytes if the
1126 to read all data). Chunks may be less than size bytes if the
1123 chunk is the last chunk in the file, or the file is a socket or
1127 chunk is the last chunk in the file, or the file is a socket or
1124 some other type of file that sometimes reads less data than is
1128 some other type of file that sometimes reads less data than is
1125 requested."""
1129 requested."""
1126 assert size >= 0
1130 assert size >= 0
1127 assert limit is None or limit >= 0
1131 assert limit is None or limit >= 0
1128 while True:
1132 while True:
1129 if limit is None: nbytes = size
1133 if limit is None: nbytes = size
1130 else: nbytes = min(limit, size)
1134 else: nbytes = min(limit, size)
1131 s = nbytes and f.read(nbytes)
1135 s = nbytes and f.read(nbytes)
1132 if not s: break
1136 if not s: break
1133 if limit: limit -= len(s)
1137 if limit: limit -= len(s)
1134 yield s
1138 yield s
1135
1139
1136 def makedate():
1140 def makedate():
1137 lt = time.localtime()
1141 lt = time.localtime()
1138 if lt[8] == 1 and time.daylight:
1142 if lt[8] == 1 and time.daylight:
1139 tz = time.altzone
1143 tz = time.altzone
1140 else:
1144 else:
1141 tz = time.timezone
1145 tz = time.timezone
1142 return time.mktime(lt), tz
1146 return time.mktime(lt), tz
1143
1147
1144 def datestr(date=None, format='%a %b %d %H:%M:%S %Y', timezone=True):
1148 def datestr(date=None, format='%a %b %d %H:%M:%S %Y', timezone=True):
1145 """represent a (unixtime, offset) tuple as a localized time.
1149 """represent a (unixtime, offset) tuple as a localized time.
1146 unixtime is seconds since the epoch, and offset is the time zone's
1150 unixtime is seconds since the epoch, and offset is the time zone's
1147 number of seconds away from UTC. if timezone is false, do not
1151 number of seconds away from UTC. if timezone is false, do not
1148 append time zone to string."""
1152 append time zone to string."""
1149 t, tz = date or makedate()
1153 t, tz = date or makedate()
1150 s = time.strftime(format, time.gmtime(float(t) - tz))
1154 s = time.strftime(format, time.gmtime(float(t) - tz))
1151 if timezone:
1155 if timezone:
1152 s += " %+03d%02d" % (-tz / 3600, ((-tz % 3600) / 60))
1156 s += " %+03d%02d" % (-tz / 3600, ((-tz % 3600) / 60))
1153 return s
1157 return s
1154
1158
1155 def strdate(string, format, defaults):
1159 def strdate(string, format, defaults):
1156 """parse a localized time string and return a (unixtime, offset) tuple.
1160 """parse a localized time string and return a (unixtime, offset) tuple.
1157 if the string cannot be parsed, ValueError is raised."""
1161 if the string cannot be parsed, ValueError is raised."""
1158 def timezone(string):
1162 def timezone(string):
1159 tz = string.split()[-1]
1163 tz = string.split()[-1]
1160 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
1164 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
1161 tz = int(tz)
1165 tz = int(tz)
1162 offset = - 3600 * (tz / 100) - 60 * (tz % 100)
1166 offset = - 3600 * (tz / 100) - 60 * (tz % 100)
1163 return offset
1167 return offset
1164 if tz == "GMT" or tz == "UTC":
1168 if tz == "GMT" or tz == "UTC":
1165 return 0
1169 return 0
1166 return None
1170 return None
1167
1171
1168 # NOTE: unixtime = localunixtime + offset
1172 # NOTE: unixtime = localunixtime + offset
1169 offset, date = timezone(string), string
1173 offset, date = timezone(string), string
1170 if offset != None:
1174 if offset != None:
1171 date = " ".join(string.split()[:-1])
1175 date = " ".join(string.split()[:-1])
1172
1176
1173 # add missing elements from defaults
1177 # add missing elements from defaults
1174 for part in defaults:
1178 for part in defaults:
1175 found = [True for p in part if ("%"+p) in format]
1179 found = [True for p in part if ("%"+p) in format]
1176 if not found:
1180 if not found:
1177 date += "@" + defaults[part]
1181 date += "@" + defaults[part]
1178 format += "@%" + part[0]
1182 format += "@%" + part[0]
1179
1183
1180 timetuple = time.strptime(date, format)
1184 timetuple = time.strptime(date, format)
1181 localunixtime = int(calendar.timegm(timetuple))
1185 localunixtime = int(calendar.timegm(timetuple))
1182 if offset is None:
1186 if offset is None:
1183 # local timezone
1187 # local timezone
1184 unixtime = int(time.mktime(timetuple))
1188 unixtime = int(time.mktime(timetuple))
1185 offset = unixtime - localunixtime
1189 offset = unixtime - localunixtime
1186 else:
1190 else:
1187 unixtime = localunixtime + offset
1191 unixtime = localunixtime + offset
1188 return unixtime, offset
1192 return unixtime, offset
1189
1193
1190 def parsedate(string, formats=None, defaults=None):
1194 def parsedate(string, formats=None, defaults=None):
1191 """parse a localized time string and return a (unixtime, offset) tuple.
1195 """parse a localized time string and return a (unixtime, offset) tuple.
1192 The date may be a "unixtime offset" string or in one of the specified
1196 The date may be a "unixtime offset" string or in one of the specified
1193 formats."""
1197 formats."""
1194 if not string:
1198 if not string:
1195 return 0, 0
1199 return 0, 0
1196 if not formats:
1200 if not formats:
1197 formats = defaultdateformats
1201 formats = defaultdateformats
1198 string = string.strip()
1202 string = string.strip()
1199 try:
1203 try:
1200 when, offset = map(int, string.split(' '))
1204 when, offset = map(int, string.split(' '))
1201 except ValueError:
1205 except ValueError:
1202 # fill out defaults
1206 # fill out defaults
1203 if not defaults:
1207 if not defaults:
1204 defaults = {}
1208 defaults = {}
1205 now = makedate()
1209 now = makedate()
1206 for part in "d mb yY HI M S".split():
1210 for part in "d mb yY HI M S".split():
1207 if part not in defaults:
1211 if part not in defaults:
1208 if part[0] in "HMS":
1212 if part[0] in "HMS":
1209 defaults[part] = "00"
1213 defaults[part] = "00"
1210 elif part[0] in "dm":
1214 elif part[0] in "dm":
1211 defaults[part] = "1"
1215 defaults[part] = "1"
1212 else:
1216 else:
1213 defaults[part] = datestr(now, "%" + part[0], False)
1217 defaults[part] = datestr(now, "%" + part[0], False)
1214
1218
1215 for format in formats:
1219 for format in formats:
1216 try:
1220 try:
1217 when, offset = strdate(string, format, defaults)
1221 when, offset = strdate(string, format, defaults)
1218 except ValueError:
1222 except ValueError:
1219 pass
1223 pass
1220 else:
1224 else:
1221 break
1225 break
1222 else:
1226 else:
1223 raise Abort(_('invalid date: %r ') % string)
1227 raise Abort(_('invalid date: %r ') % string)
1224 # validate explicit (probably user-specified) date and
1228 # validate explicit (probably user-specified) date and
1225 # time zone offset. values must fit in signed 32 bits for
1229 # time zone offset. values must fit in signed 32 bits for
1226 # current 32-bit linux runtimes. timezones go from UTC-12
1230 # current 32-bit linux runtimes. timezones go from UTC-12
1227 # to UTC+14
1231 # to UTC+14
1228 if abs(when) > 0x7fffffff:
1232 if abs(when) > 0x7fffffff:
1229 raise Abort(_('date exceeds 32 bits: %d') % when)
1233 raise Abort(_('date exceeds 32 bits: %d') % when)
1230 if offset < -50400 or offset > 43200:
1234 if offset < -50400 or offset > 43200:
1231 raise Abort(_('impossible time zone offset: %d') % offset)
1235 raise Abort(_('impossible time zone offset: %d') % offset)
1232 return when, offset
1236 return when, offset
1233
1237
1234 def matchdate(date):
1238 def matchdate(date):
1235 """Return a function that matches a given date match specifier
1239 """Return a function that matches a given date match specifier
1236
1240
1237 Formats include:
1241 Formats include:
1238
1242
1239 '{date}' match a given date to the accuracy provided
1243 '{date}' match a given date to the accuracy provided
1240
1244
1241 '<{date}' on or before a given date
1245 '<{date}' on or before a given date
1242
1246
1243 '>{date}' on or after a given date
1247 '>{date}' on or after a given date
1244
1248
1245 """
1249 """
1246
1250
1247 def lower(date):
1251 def lower(date):
1248 return parsedate(date, extendeddateformats)[0]
1252 return parsedate(date, extendeddateformats)[0]
1249
1253
1250 def upper(date):
1254 def upper(date):
1251 d = dict(mb="12", HI="23", M="59", S="59")
1255 d = dict(mb="12", HI="23", M="59", S="59")
1252 for days in "31 30 29".split():
1256 for days in "31 30 29".split():
1253 try:
1257 try:
1254 d["d"] = days
1258 d["d"] = days
1255 return parsedate(date, extendeddateformats, d)[0]
1259 return parsedate(date, extendeddateformats, d)[0]
1256 except:
1260 except:
1257 pass
1261 pass
1258 d["d"] = "28"
1262 d["d"] = "28"
1259 return parsedate(date, extendeddateformats, d)[0]
1263 return parsedate(date, extendeddateformats, d)[0]
1260
1264
1261 if date[0] == "<":
1265 if date[0] == "<":
1262 when = upper(date[1:])
1266 when = upper(date[1:])
1263 return lambda x: x <= when
1267 return lambda x: x <= when
1264 elif date[0] == ">":
1268 elif date[0] == ">":
1265 when = lower(date[1:])
1269 when = lower(date[1:])
1266 return lambda x: x >= when
1270 return lambda x: x >= when
1267 elif date[0] == "-":
1271 elif date[0] == "-":
1268 try:
1272 try:
1269 days = int(date[1:])
1273 days = int(date[1:])
1270 except ValueError:
1274 except ValueError:
1271 raise Abort(_("invalid day spec: %s") % date[1:])
1275 raise Abort(_("invalid day spec: %s") % date[1:])
1272 when = makedate()[0] - days * 3600 * 24
1276 when = makedate()[0] - days * 3600 * 24
1273 return lambda x: x >= when
1277 return lambda x: x >= when
1274 elif " to " in date:
1278 elif " to " in date:
1275 a, b = date.split(" to ")
1279 a, b = date.split(" to ")
1276 start, stop = lower(a), upper(b)
1280 start, stop = lower(a), upper(b)
1277 return lambda x: x >= start and x <= stop
1281 return lambda x: x >= start and x <= stop
1278 else:
1282 else:
1279 start, stop = lower(date), upper(date)
1283 start, stop = lower(date), upper(date)
1280 return lambda x: x >= start and x <= stop
1284 return lambda x: x >= start and x <= stop
1281
1285
1282 def shortuser(user):
1286 def shortuser(user):
1283 """Return a short representation of a user name or email address."""
1287 """Return a short representation of a user name or email address."""
1284 f = user.find('@')
1288 f = user.find('@')
1285 if f >= 0:
1289 if f >= 0:
1286 user = user[:f]
1290 user = user[:f]
1287 f = user.find('<')
1291 f = user.find('<')
1288 if f >= 0:
1292 if f >= 0:
1289 user = user[f+1:]
1293 user = user[f+1:]
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]
1299 user = user[:f]
1296 return user
1300 return user
1297
1301
1298 def ellipsis(text, maxlength=400):
1302 def ellipsis(text, maxlength=400):
1299 """Trim string to at most maxlength (default: 400) characters."""
1303 """Trim string to at most maxlength (default: 400) characters."""
1300 if len(text) <= maxlength:
1304 if len(text) <= maxlength:
1301 return text
1305 return text
1302 else:
1306 else:
1303 return "%s..." % (text[:maxlength-3])
1307 return "%s..." % (text[:maxlength-3])
1304
1308
1305 def walkrepos(path):
1309 def walkrepos(path):
1306 '''yield every hg repository under path, recursively.'''
1310 '''yield every hg repository under path, recursively.'''
1307 def errhandler(err):
1311 def errhandler(err):
1308 if err.filename == path:
1312 if err.filename == path:
1309 raise err
1313 raise err
1310
1314
1311 for root, dirs, files in os.walk(path, onerror=errhandler):
1315 for root, dirs, files in os.walk(path, onerror=errhandler):
1312 for d in dirs:
1316 for d in dirs:
1313 if d == '.hg':
1317 if d == '.hg':
1314 yield root
1318 yield root
1315 dirs[:] = []
1319 dirs[:] = []
1316 break
1320 break
1317
1321
1318 _rcpath = None
1322 _rcpath = None
1319
1323
1320 def rcpath():
1324 def rcpath():
1321 '''return hgrc search path. if env var HGRCPATH is set, use it.
1325 '''return hgrc search path. if env var HGRCPATH is set, use it.
1322 for each item in path, if directory, use files ending in .rc,
1326 for each item in path, if directory, use files ending in .rc,
1323 else use item.
1327 else use item.
1324 make HGRCPATH empty to only look in .hg/hgrc of current repo.
1328 make HGRCPATH empty to only look in .hg/hgrc of current repo.
1325 if no HGRCPATH, use default os-specific path.'''
1329 if no HGRCPATH, use default os-specific path.'''
1326 global _rcpath
1330 global _rcpath
1327 if _rcpath is None:
1331 if _rcpath is None:
1328 if 'HGRCPATH' in os.environ:
1332 if 'HGRCPATH' in os.environ:
1329 _rcpath = []
1333 _rcpath = []
1330 for p in os.environ['HGRCPATH'].split(os.pathsep):
1334 for p in os.environ['HGRCPATH'].split(os.pathsep):
1331 if not p: continue
1335 if not p: continue
1332 if os.path.isdir(p):
1336 if os.path.isdir(p):
1333 for f in os.listdir(p):
1337 for f in os.listdir(p):
1334 if f.endswith('.rc'):
1338 if f.endswith('.rc'):
1335 _rcpath.append(os.path.join(p, f))
1339 _rcpath.append(os.path.join(p, f))
1336 else:
1340 else:
1337 _rcpath.append(p)
1341 _rcpath.append(p)
1338 else:
1342 else:
1339 _rcpath = os_rcpath()
1343 _rcpath = os_rcpath()
1340 return _rcpath
1344 return _rcpath
1341
1345
1342 def bytecount(nbytes):
1346 def bytecount(nbytes):
1343 '''return byte count formatted as readable string, with units'''
1347 '''return byte count formatted as readable string, with units'''
1344
1348
1345 units = (
1349 units = (
1346 (100, 1<<30, _('%.0f GB')),
1350 (100, 1<<30, _('%.0f GB')),
1347 (10, 1<<30, _('%.1f GB')),
1351 (10, 1<<30, _('%.1f GB')),
1348 (1, 1<<30, _('%.2f GB')),
1352 (1, 1<<30, _('%.2f GB')),
1349 (100, 1<<20, _('%.0f MB')),
1353 (100, 1<<20, _('%.0f MB')),
1350 (10, 1<<20, _('%.1f MB')),
1354 (10, 1<<20, _('%.1f MB')),
1351 (1, 1<<20, _('%.2f MB')),
1355 (1, 1<<20, _('%.2f MB')),
1352 (100, 1<<10, _('%.0f KB')),
1356 (100, 1<<10, _('%.0f KB')),
1353 (10, 1<<10, _('%.1f KB')),
1357 (10, 1<<10, _('%.1f KB')),
1354 (1, 1<<10, _('%.2f KB')),
1358 (1, 1<<10, _('%.2f KB')),
1355 (1, 1, _('%.0f bytes')),
1359 (1, 1, _('%.0f bytes')),
1356 )
1360 )
1357
1361
1358 for multiplier, divisor, format in units:
1362 for multiplier, divisor, format in units:
1359 if nbytes >= divisor * multiplier:
1363 if nbytes >= divisor * multiplier:
1360 return format % (nbytes / float(divisor))
1364 return format % (nbytes / float(divisor))
1361 return units[-1][2] % nbytes
1365 return units[-1][2] % nbytes
1362
1366
1363 def drop_scheme(scheme, path):
1367 def drop_scheme(scheme, path):
1364 sc = scheme + ':'
1368 sc = scheme + ':'
1365 if path.startswith(sc):
1369 if path.startswith(sc):
1366 path = path[len(sc):]
1370 path = path[len(sc):]
1367 if path.startswith('//'):
1371 if path.startswith('//'):
1368 path = path[2:]
1372 path = path[2:]
1369 return path
1373 return path
General Comments 0
You need to be logged in to leave comments. Login now