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