##// END OF EJS Templates
symlinks: check whether a filesystem supports symlinks
Matt Mackall -
r3998:315d4799 default
parent child Browse files
Show More
@@ -1,1341 +1,1353 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):
717 """check whether the given path is on a symlink-capable filesystem"""
718 # mktemp is not racy because symlink creation will fail if the
719 # file already exists
720 name = tempfile.mktemp(dir=path)
721 try:
722 os.symlink(".", name)
723 os.unlink(name)
724 return True
725 except OSError:
726 return False
727
716 # Platform specific variants
728 # Platform specific variants
717 if os.name == 'nt':
729 if os.name == 'nt':
718 import msvcrt
730 import msvcrt
719 nulldev = 'NUL:'
731 nulldev = 'NUL:'
720
732
721 class winstdout:
733 class winstdout:
722 '''stdout on windows misbehaves if sent through a pipe'''
734 '''stdout on windows misbehaves if sent through a pipe'''
723
735
724 def __init__(self, fp):
736 def __init__(self, fp):
725 self.fp = fp
737 self.fp = fp
726
738
727 def __getattr__(self, key):
739 def __getattr__(self, key):
728 return getattr(self.fp, key)
740 return getattr(self.fp, key)
729
741
730 def close(self):
742 def close(self):
731 try:
743 try:
732 self.fp.close()
744 self.fp.close()
733 except: pass
745 except: pass
734
746
735 def write(self, s):
747 def write(self, s):
736 try:
748 try:
737 return self.fp.write(s)
749 return self.fp.write(s)
738 except IOError, inst:
750 except IOError, inst:
739 if inst.errno != 0: raise
751 if inst.errno != 0: raise
740 self.close()
752 self.close()
741 raise IOError(errno.EPIPE, 'Broken pipe')
753 raise IOError(errno.EPIPE, 'Broken pipe')
742
754
743 sys.stdout = winstdout(sys.stdout)
755 sys.stdout = winstdout(sys.stdout)
744
756
745 def system_rcpath():
757 def system_rcpath():
746 try:
758 try:
747 return system_rcpath_win32()
759 return system_rcpath_win32()
748 except:
760 except:
749 return [r'c:\mercurial\mercurial.ini']
761 return [r'c:\mercurial\mercurial.ini']
750
762
751 def os_rcpath():
763 def os_rcpath():
752 '''return default os-specific hgrc search path'''
764 '''return default os-specific hgrc search path'''
753 path = system_rcpath()
765 path = system_rcpath()
754 path.append(user_rcpath())
766 path.append(user_rcpath())
755 userprofile = os.environ.get('USERPROFILE')
767 userprofile = os.environ.get('USERPROFILE')
756 if userprofile:
768 if userprofile:
757 path.append(os.path.join(userprofile, 'mercurial.ini'))
769 path.append(os.path.join(userprofile, 'mercurial.ini'))
758 return path
770 return path
759
771
760 def user_rcpath():
772 def user_rcpath():
761 '''return os-specific hgrc search path to the user dir'''
773 '''return os-specific hgrc search path to the user dir'''
762 return os.path.join(os.path.expanduser('~'), 'mercurial.ini')
774 return os.path.join(os.path.expanduser('~'), 'mercurial.ini')
763
775
764 def parse_patch_output(output_line):
776 def parse_patch_output(output_line):
765 """parses the output produced by patch and returns the file name"""
777 """parses the output produced by patch and returns the file name"""
766 pf = output_line[14:]
778 pf = output_line[14:]
767 if pf[0] == '`':
779 if pf[0] == '`':
768 pf = pf[1:-1] # Remove the quotes
780 pf = pf[1:-1] # Remove the quotes
769 return pf
781 return pf
770
782
771 def testpid(pid):
783 def testpid(pid):
772 '''return False if pid dead, True if running or not known'''
784 '''return False if pid dead, True if running or not known'''
773 return True
785 return True
774
786
775 def set_exec(f, mode):
787 def set_exec(f, mode):
776 pass
788 pass
777
789
778 def set_binary(fd):
790 def set_binary(fd):
779 msvcrt.setmode(fd.fileno(), os.O_BINARY)
791 msvcrt.setmode(fd.fileno(), os.O_BINARY)
780
792
781 def pconvert(path):
793 def pconvert(path):
782 return path.replace("\\", "/")
794 return path.replace("\\", "/")
783
795
784 def localpath(path):
796 def localpath(path):
785 return path.replace('/', '\\')
797 return path.replace('/', '\\')
786
798
787 def normpath(path):
799 def normpath(path):
788 return pconvert(os.path.normpath(path))
800 return pconvert(os.path.normpath(path))
789
801
790 makelock = _makelock_file
802 makelock = _makelock_file
791 readlock = _readlock_file
803 readlock = _readlock_file
792
804
793 def samestat(s1, s2):
805 def samestat(s1, s2):
794 return False
806 return False
795
807
796 def shellquote(s):
808 def shellquote(s):
797 return '"%s"' % s.replace('"', '\\"')
809 return '"%s"' % s.replace('"', '\\"')
798
810
799 def explain_exit(code):
811 def explain_exit(code):
800 return _("exited with status %d") % code, code
812 return _("exited with status %d") % code, code
801
813
802 # if you change this stub into a real check, please try to implement the
814 # if you change this stub into a real check, please try to implement the
803 # username and groupname functions above, too.
815 # username and groupname functions above, too.
804 def isowner(fp, st=None):
816 def isowner(fp, st=None):
805 return True
817 return True
806
818
807 try:
819 try:
808 # override functions with win32 versions if possible
820 # override functions with win32 versions if possible
809 from util_win32 import *
821 from util_win32 import *
810 if not is_win_9x():
822 if not is_win_9x():
811 posixfile = posixfile_nt
823 posixfile = posixfile_nt
812 except ImportError:
824 except ImportError:
813 pass
825 pass
814
826
815 else:
827 else:
816 nulldev = '/dev/null'
828 nulldev = '/dev/null'
817 _umask = os.umask(0)
829 _umask = os.umask(0)
818 os.umask(_umask)
830 os.umask(_umask)
819
831
820 def rcfiles(path):
832 def rcfiles(path):
821 rcs = [os.path.join(path, 'hgrc')]
833 rcs = [os.path.join(path, 'hgrc')]
822 rcdir = os.path.join(path, 'hgrc.d')
834 rcdir = os.path.join(path, 'hgrc.d')
823 try:
835 try:
824 rcs.extend([os.path.join(rcdir, f) for f in os.listdir(rcdir)
836 rcs.extend([os.path.join(rcdir, f) for f in os.listdir(rcdir)
825 if f.endswith(".rc")])
837 if f.endswith(".rc")])
826 except OSError:
838 except OSError:
827 pass
839 pass
828 return rcs
840 return rcs
829
841
830 def os_rcpath():
842 def os_rcpath():
831 '''return default os-specific hgrc search path'''
843 '''return default os-specific hgrc search path'''
832 path = []
844 path = []
833 # old mod_python does not set sys.argv
845 # old mod_python does not set sys.argv
834 if len(getattr(sys, 'argv', [])) > 0:
846 if len(getattr(sys, 'argv', [])) > 0:
835 path.extend(rcfiles(os.path.dirname(sys.argv[0]) +
847 path.extend(rcfiles(os.path.dirname(sys.argv[0]) +
836 '/../etc/mercurial'))
848 '/../etc/mercurial'))
837 path.extend(rcfiles('/etc/mercurial'))
849 path.extend(rcfiles('/etc/mercurial'))
838 path.append(os.path.expanduser('~/.hgrc'))
850 path.append(os.path.expanduser('~/.hgrc'))
839 path = [os.path.normpath(f) for f in path]
851 path = [os.path.normpath(f) for f in path]
840 return path
852 return path
841
853
842 def parse_patch_output(output_line):
854 def parse_patch_output(output_line):
843 """parses the output produced by patch and returns the file name"""
855 """parses the output produced by patch and returns the file name"""
844 pf = output_line[14:]
856 pf = output_line[14:]
845 if pf.startswith("'") and pf.endswith("'") and " " in pf:
857 if pf.startswith("'") and pf.endswith("'") and " " in pf:
846 pf = pf[1:-1] # Remove the quotes
858 pf = pf[1:-1] # Remove the quotes
847 return pf
859 return pf
848
860
849 def is_exec(f):
861 def is_exec(f):
850 """check whether a file is executable"""
862 """check whether a file is executable"""
851 return (os.lstat(f).st_mode & 0100 != 0)
863 return (os.lstat(f).st_mode & 0100 != 0)
852
864
853 def set_exec(f, mode):
865 def set_exec(f, mode):
854 s = os.lstat(f).st_mode
866 s = os.lstat(f).st_mode
855 if (s & 0100 != 0) == mode:
867 if (s & 0100 != 0) == mode:
856 return
868 return
857 if mode:
869 if mode:
858 # Turn on +x for every +r bit when making a file executable
870 # Turn on +x for every +r bit when making a file executable
859 # and obey umask.
871 # and obey umask.
860 os.chmod(f, s | (s & 0444) >> 2 & ~_umask)
872 os.chmod(f, s | (s & 0444) >> 2 & ~_umask)
861 else:
873 else:
862 os.chmod(f, s & 0666)
874 os.chmod(f, s & 0666)
863
875
864 def set_binary(fd):
876 def set_binary(fd):
865 pass
877 pass
866
878
867 def pconvert(path):
879 def pconvert(path):
868 return path
880 return path
869
881
870 def localpath(path):
882 def localpath(path):
871 return path
883 return path
872
884
873 normpath = os.path.normpath
885 normpath = os.path.normpath
874 samestat = os.path.samestat
886 samestat = os.path.samestat
875
887
876 def makelock(info, pathname):
888 def makelock(info, pathname):
877 try:
889 try:
878 os.symlink(info, pathname)
890 os.symlink(info, pathname)
879 except OSError, why:
891 except OSError, why:
880 if why.errno == errno.EEXIST:
892 if why.errno == errno.EEXIST:
881 raise
893 raise
882 else:
894 else:
883 _makelock_file(info, pathname)
895 _makelock_file(info, pathname)
884
896
885 def readlock(pathname):
897 def readlock(pathname):
886 try:
898 try:
887 return os.readlink(pathname)
899 return os.readlink(pathname)
888 except OSError, why:
900 except OSError, why:
889 if why.errno == errno.EINVAL:
901 if why.errno == errno.EINVAL:
890 return _readlock_file(pathname)
902 return _readlock_file(pathname)
891 else:
903 else:
892 raise
904 raise
893
905
894 def shellquote(s):
906 def shellquote(s):
895 return "'%s'" % s.replace("'", "'\\''")
907 return "'%s'" % s.replace("'", "'\\''")
896
908
897 def testpid(pid):
909 def testpid(pid):
898 '''return False if pid dead, True if running or not sure'''
910 '''return False if pid dead, True if running or not sure'''
899 try:
911 try:
900 os.kill(pid, 0)
912 os.kill(pid, 0)
901 return True
913 return True
902 except OSError, inst:
914 except OSError, inst:
903 return inst.errno != errno.ESRCH
915 return inst.errno != errno.ESRCH
904
916
905 def explain_exit(code):
917 def explain_exit(code):
906 """return a 2-tuple (desc, code) describing a process's status"""
918 """return a 2-tuple (desc, code) describing a process's status"""
907 if os.WIFEXITED(code):
919 if os.WIFEXITED(code):
908 val = os.WEXITSTATUS(code)
920 val = os.WEXITSTATUS(code)
909 return _("exited with status %d") % val, val
921 return _("exited with status %d") % val, val
910 elif os.WIFSIGNALED(code):
922 elif os.WIFSIGNALED(code):
911 val = os.WTERMSIG(code)
923 val = os.WTERMSIG(code)
912 return _("killed by signal %d") % val, val
924 return _("killed by signal %d") % val, val
913 elif os.WIFSTOPPED(code):
925 elif os.WIFSTOPPED(code):
914 val = os.WSTOPSIG(code)
926 val = os.WSTOPSIG(code)
915 return _("stopped by signal %d") % val, val
927 return _("stopped by signal %d") % val, val
916 raise ValueError(_("invalid exit code"))
928 raise ValueError(_("invalid exit code"))
917
929
918 def isowner(fp, st=None):
930 def isowner(fp, st=None):
919 """Return True if the file object f belongs to the current user.
931 """Return True if the file object f belongs to the current user.
920
932
921 The return value of a util.fstat(f) may be passed as the st argument.
933 The return value of a util.fstat(f) may be passed as the st argument.
922 """
934 """
923 if st is None:
935 if st is None:
924 st = fstat(fp)
936 st = fstat(fp)
925 return st.st_uid == os.getuid()
937 return st.st_uid == os.getuid()
926
938
927 def _buildencodefun():
939 def _buildencodefun():
928 e = '_'
940 e = '_'
929 win_reserved = [ord(x) for x in '\\:*?"<>|']
941 win_reserved = [ord(x) for x in '\\:*?"<>|']
930 cmap = dict([ (chr(x), chr(x)) for x in xrange(127) ])
942 cmap = dict([ (chr(x), chr(x)) for x in xrange(127) ])
931 for x in (range(32) + range(126, 256) + win_reserved):
943 for x in (range(32) + range(126, 256) + win_reserved):
932 cmap[chr(x)] = "~%02x" % x
944 cmap[chr(x)] = "~%02x" % x
933 for x in range(ord("A"), ord("Z")+1) + [ord(e)]:
945 for x in range(ord("A"), ord("Z")+1) + [ord(e)]:
934 cmap[chr(x)] = e + chr(x).lower()
946 cmap[chr(x)] = e + chr(x).lower()
935 dmap = {}
947 dmap = {}
936 for k, v in cmap.iteritems():
948 for k, v in cmap.iteritems():
937 dmap[v] = k
949 dmap[v] = k
938 def decode(s):
950 def decode(s):
939 i = 0
951 i = 0
940 while i < len(s):
952 while i < len(s):
941 for l in xrange(1, 4):
953 for l in xrange(1, 4):
942 try:
954 try:
943 yield dmap[s[i:i+l]]
955 yield dmap[s[i:i+l]]
944 i += l
956 i += l
945 break
957 break
946 except KeyError:
958 except KeyError:
947 pass
959 pass
948 else:
960 else:
949 raise KeyError
961 raise KeyError
950 return (lambda s: "".join([cmap[c] for c in s]),
962 return (lambda s: "".join([cmap[c] for c in s]),
951 lambda s: "".join(list(decode(s))))
963 lambda s: "".join(list(decode(s))))
952
964
953 encodefilename, decodefilename = _buildencodefun()
965 encodefilename, decodefilename = _buildencodefun()
954
966
955 def encodedopener(openerfn, fn):
967 def encodedopener(openerfn, fn):
956 def o(path, *args, **kw):
968 def o(path, *args, **kw):
957 return openerfn(fn(path), *args, **kw)
969 return openerfn(fn(path), *args, **kw)
958 return o
970 return o
959
971
960 def opener(base, audit=True):
972 def opener(base, audit=True):
961 """
973 """
962 return a function that opens files relative to base
974 return a function that opens files relative to base
963
975
964 this function is used to hide the details of COW semantics and
976 this function is used to hide the details of COW semantics and
965 remote file access from higher level code.
977 remote file access from higher level code.
966 """
978 """
967 p = base
979 p = base
968 audit_p = audit
980 audit_p = audit
969
981
970 def mktempcopy(name):
982 def mktempcopy(name):
971 d, fn = os.path.split(name)
983 d, fn = os.path.split(name)
972 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
984 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
973 os.close(fd)
985 os.close(fd)
974 ofp = posixfile(temp, "wb")
986 ofp = posixfile(temp, "wb")
975 try:
987 try:
976 try:
988 try:
977 ifp = posixfile(name, "rb")
989 ifp = posixfile(name, "rb")
978 except IOError, inst:
990 except IOError, inst:
979 if not getattr(inst, 'filename', None):
991 if not getattr(inst, 'filename', None):
980 inst.filename = name
992 inst.filename = name
981 raise
993 raise
982 for chunk in filechunkiter(ifp):
994 for chunk in filechunkiter(ifp):
983 ofp.write(chunk)
995 ofp.write(chunk)
984 ifp.close()
996 ifp.close()
985 ofp.close()
997 ofp.close()
986 except:
998 except:
987 try: os.unlink(temp)
999 try: os.unlink(temp)
988 except: pass
1000 except: pass
989 raise
1001 raise
990 st = os.lstat(name)
1002 st = os.lstat(name)
991 os.chmod(temp, st.st_mode)
1003 os.chmod(temp, st.st_mode)
992 return temp
1004 return temp
993
1005
994 class atomictempfile(posixfile):
1006 class atomictempfile(posixfile):
995 """the file will only be copied when rename is called"""
1007 """the file will only be copied when rename is called"""
996 def __init__(self, name, mode):
1008 def __init__(self, name, mode):
997 self.__name = name
1009 self.__name = name
998 self.temp = mktempcopy(name)
1010 self.temp = mktempcopy(name)
999 posixfile.__init__(self, self.temp, mode)
1011 posixfile.__init__(self, self.temp, mode)
1000 def rename(self):
1012 def rename(self):
1001 if not self.closed:
1013 if not self.closed:
1002 posixfile.close(self)
1014 posixfile.close(self)
1003 rename(self.temp, localpath(self.__name))
1015 rename(self.temp, localpath(self.__name))
1004 def __del__(self):
1016 def __del__(self):
1005 if not self.closed:
1017 if not self.closed:
1006 try:
1018 try:
1007 os.unlink(self.temp)
1019 os.unlink(self.temp)
1008 except: pass
1020 except: pass
1009 posixfile.close(self)
1021 posixfile.close(self)
1010
1022
1011 class atomicfile(atomictempfile):
1023 class atomicfile(atomictempfile):
1012 """the file will only be copied on close"""
1024 """the file will only be copied on close"""
1013 def __init__(self, name, mode):
1025 def __init__(self, name, mode):
1014 atomictempfile.__init__(self, name, mode)
1026 atomictempfile.__init__(self, name, mode)
1015 def close(self):
1027 def close(self):
1016 self.rename()
1028 self.rename()
1017 def __del__(self):
1029 def __del__(self):
1018 self.rename()
1030 self.rename()
1019
1031
1020 def o(path, mode="r", text=False, atomic=False, atomictemp=False):
1032 def o(path, mode="r", text=False, atomic=False, atomictemp=False):
1021 if audit_p:
1033 if audit_p:
1022 audit_path(path)
1034 audit_path(path)
1023 f = os.path.join(p, path)
1035 f = os.path.join(p, path)
1024
1036
1025 if not text:
1037 if not text:
1026 mode += "b" # for that other OS
1038 mode += "b" # for that other OS
1027
1039
1028 if mode[0] != "r":
1040 if mode[0] != "r":
1029 try:
1041 try:
1030 nlink = nlinks(f)
1042 nlink = nlinks(f)
1031 except OSError:
1043 except OSError:
1032 d = os.path.dirname(f)
1044 d = os.path.dirname(f)
1033 if not os.path.isdir(d):
1045 if not os.path.isdir(d):
1034 os.makedirs(d)
1046 os.makedirs(d)
1035 else:
1047 else:
1036 if atomic:
1048 if atomic:
1037 return atomicfile(f, mode)
1049 return atomicfile(f, mode)
1038 elif atomictemp:
1050 elif atomictemp:
1039 return atomictempfile(f, mode)
1051 return atomictempfile(f, mode)
1040 if nlink > 1:
1052 if nlink > 1:
1041 rename(mktempcopy(f), f)
1053 rename(mktempcopy(f), f)
1042 return posixfile(f, mode)
1054 return posixfile(f, mode)
1043
1055
1044 return o
1056 return o
1045
1057
1046 class chunkbuffer(object):
1058 class chunkbuffer(object):
1047 """Allow arbitrary sized chunks of data to be efficiently read from an
1059 """Allow arbitrary sized chunks of data to be efficiently read from an
1048 iterator over chunks of arbitrary size."""
1060 iterator over chunks of arbitrary size."""
1049
1061
1050 def __init__(self, in_iter, targetsize = 2**16):
1062 def __init__(self, in_iter, targetsize = 2**16):
1051 """in_iter is the iterator that's iterating over the input chunks.
1063 """in_iter is the iterator that's iterating over the input chunks.
1052 targetsize is how big a buffer to try to maintain."""
1064 targetsize is how big a buffer to try to maintain."""
1053 self.in_iter = iter(in_iter)
1065 self.in_iter = iter(in_iter)
1054 self.buf = ''
1066 self.buf = ''
1055 self.targetsize = int(targetsize)
1067 self.targetsize = int(targetsize)
1056 if self.targetsize <= 0:
1068 if self.targetsize <= 0:
1057 raise ValueError(_("targetsize must be greater than 0, was %d") %
1069 raise ValueError(_("targetsize must be greater than 0, was %d") %
1058 targetsize)
1070 targetsize)
1059 self.iterempty = False
1071 self.iterempty = False
1060
1072
1061 def fillbuf(self):
1073 def fillbuf(self):
1062 """Ignore target size; read every chunk from iterator until empty."""
1074 """Ignore target size; read every chunk from iterator until empty."""
1063 if not self.iterempty:
1075 if not self.iterempty:
1064 collector = cStringIO.StringIO()
1076 collector = cStringIO.StringIO()
1065 collector.write(self.buf)
1077 collector.write(self.buf)
1066 for ch in self.in_iter:
1078 for ch in self.in_iter:
1067 collector.write(ch)
1079 collector.write(ch)
1068 self.buf = collector.getvalue()
1080 self.buf = collector.getvalue()
1069 self.iterempty = True
1081 self.iterempty = True
1070
1082
1071 def read(self, l):
1083 def read(self, l):
1072 """Read L bytes of data from the iterator of chunks of data.
1084 """Read L bytes of data from the iterator of chunks of data.
1073 Returns less than L bytes if the iterator runs dry."""
1085 Returns less than L bytes if the iterator runs dry."""
1074 if l > len(self.buf) and not self.iterempty:
1086 if l > len(self.buf) and not self.iterempty:
1075 # Clamp to a multiple of self.targetsize
1087 # Clamp to a multiple of self.targetsize
1076 targetsize = self.targetsize * ((l // self.targetsize) + 1)
1088 targetsize = self.targetsize * ((l // self.targetsize) + 1)
1077 collector = cStringIO.StringIO()
1089 collector = cStringIO.StringIO()
1078 collector.write(self.buf)
1090 collector.write(self.buf)
1079 collected = len(self.buf)
1091 collected = len(self.buf)
1080 for chunk in self.in_iter:
1092 for chunk in self.in_iter:
1081 collector.write(chunk)
1093 collector.write(chunk)
1082 collected += len(chunk)
1094 collected += len(chunk)
1083 if collected >= targetsize:
1095 if collected >= targetsize:
1084 break
1096 break
1085 if collected < targetsize:
1097 if collected < targetsize:
1086 self.iterempty = True
1098 self.iterempty = True
1087 self.buf = collector.getvalue()
1099 self.buf = collector.getvalue()
1088 s, self.buf = self.buf[:l], buffer(self.buf, l)
1100 s, self.buf = self.buf[:l], buffer(self.buf, l)
1089 return s
1101 return s
1090
1102
1091 def filechunkiter(f, size=65536, limit=None):
1103 def filechunkiter(f, size=65536, limit=None):
1092 """Create a generator that produces the data in the file size
1104 """Create a generator that produces the data in the file size
1093 (default 65536) bytes at a time, up to optional limit (default is
1105 (default 65536) bytes at a time, up to optional limit (default is
1094 to read all data). Chunks may be less than size bytes if the
1106 to read all data). Chunks may be less than size bytes if the
1095 chunk is the last chunk in the file, or the file is a socket or
1107 chunk is the last chunk in the file, or the file is a socket or
1096 some other type of file that sometimes reads less data than is
1108 some other type of file that sometimes reads less data than is
1097 requested."""
1109 requested."""
1098 assert size >= 0
1110 assert size >= 0
1099 assert limit is None or limit >= 0
1111 assert limit is None or limit >= 0
1100 while True:
1112 while True:
1101 if limit is None: nbytes = size
1113 if limit is None: nbytes = size
1102 else: nbytes = min(limit, size)
1114 else: nbytes = min(limit, size)
1103 s = nbytes and f.read(nbytes)
1115 s = nbytes and f.read(nbytes)
1104 if not s: break
1116 if not s: break
1105 if limit: limit -= len(s)
1117 if limit: limit -= len(s)
1106 yield s
1118 yield s
1107
1119
1108 def makedate():
1120 def makedate():
1109 lt = time.localtime()
1121 lt = time.localtime()
1110 if lt[8] == 1 and time.daylight:
1122 if lt[8] == 1 and time.daylight:
1111 tz = time.altzone
1123 tz = time.altzone
1112 else:
1124 else:
1113 tz = time.timezone
1125 tz = time.timezone
1114 return time.mktime(lt), tz
1126 return time.mktime(lt), tz
1115
1127
1116 def datestr(date=None, format='%a %b %d %H:%M:%S %Y', timezone=True):
1128 def datestr(date=None, format='%a %b %d %H:%M:%S %Y', timezone=True):
1117 """represent a (unixtime, offset) tuple as a localized time.
1129 """represent a (unixtime, offset) tuple as a localized time.
1118 unixtime is seconds since the epoch, and offset is the time zone's
1130 unixtime is seconds since the epoch, and offset is the time zone's
1119 number of seconds away from UTC. if timezone is false, do not
1131 number of seconds away from UTC. if timezone is false, do not
1120 append time zone to string."""
1132 append time zone to string."""
1121 t, tz = date or makedate()
1133 t, tz = date or makedate()
1122 s = time.strftime(format, time.gmtime(float(t) - tz))
1134 s = time.strftime(format, time.gmtime(float(t) - tz))
1123 if timezone:
1135 if timezone:
1124 s += " %+03d%02d" % (-tz / 3600, ((-tz % 3600) / 60))
1136 s += " %+03d%02d" % (-tz / 3600, ((-tz % 3600) / 60))
1125 return s
1137 return s
1126
1138
1127 def strdate(string, format, defaults):
1139 def strdate(string, format, defaults):
1128 """parse a localized time string and return a (unixtime, offset) tuple.
1140 """parse a localized time string and return a (unixtime, offset) tuple.
1129 if the string cannot be parsed, ValueError is raised."""
1141 if the string cannot be parsed, ValueError is raised."""
1130 def timezone(string):
1142 def timezone(string):
1131 tz = string.split()[-1]
1143 tz = string.split()[-1]
1132 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
1144 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
1133 tz = int(tz)
1145 tz = int(tz)
1134 offset = - 3600 * (tz / 100) - 60 * (tz % 100)
1146 offset = - 3600 * (tz / 100) - 60 * (tz % 100)
1135 return offset
1147 return offset
1136 if tz == "GMT" or tz == "UTC":
1148 if tz == "GMT" or tz == "UTC":
1137 return 0
1149 return 0
1138 return None
1150 return None
1139
1151
1140 # NOTE: unixtime = localunixtime + offset
1152 # NOTE: unixtime = localunixtime + offset
1141 offset, date = timezone(string), string
1153 offset, date = timezone(string), string
1142 if offset != None:
1154 if offset != None:
1143 date = " ".join(string.split()[:-1])
1155 date = " ".join(string.split()[:-1])
1144
1156
1145 # add missing elements from defaults
1157 # add missing elements from defaults
1146 for part in defaults:
1158 for part in defaults:
1147 found = [True for p in part if ("%"+p) in format]
1159 found = [True for p in part if ("%"+p) in format]
1148 if not found:
1160 if not found:
1149 date += "@" + defaults[part]
1161 date += "@" + defaults[part]
1150 format += "@%" + part[0]
1162 format += "@%" + part[0]
1151
1163
1152 timetuple = time.strptime(date, format)
1164 timetuple = time.strptime(date, format)
1153 localunixtime = int(calendar.timegm(timetuple))
1165 localunixtime = int(calendar.timegm(timetuple))
1154 if offset is None:
1166 if offset is None:
1155 # local timezone
1167 # local timezone
1156 unixtime = int(time.mktime(timetuple))
1168 unixtime = int(time.mktime(timetuple))
1157 offset = unixtime - localunixtime
1169 offset = unixtime - localunixtime
1158 else:
1170 else:
1159 unixtime = localunixtime + offset
1171 unixtime = localunixtime + offset
1160 return unixtime, offset
1172 return unixtime, offset
1161
1173
1162 def parsedate(string, formats=None, defaults=None):
1174 def parsedate(string, formats=None, defaults=None):
1163 """parse a localized time string and return a (unixtime, offset) tuple.
1175 """parse a localized time string and return a (unixtime, offset) tuple.
1164 The date may be a "unixtime offset" string or in one of the specified
1176 The date may be a "unixtime offset" string or in one of the specified
1165 formats."""
1177 formats."""
1166 if not string:
1178 if not string:
1167 return 0, 0
1179 return 0, 0
1168 if not formats:
1180 if not formats:
1169 formats = defaultdateformats
1181 formats = defaultdateformats
1170 string = string.strip()
1182 string = string.strip()
1171 try:
1183 try:
1172 when, offset = map(int, string.split(' '))
1184 when, offset = map(int, string.split(' '))
1173 except ValueError:
1185 except ValueError:
1174 # fill out defaults
1186 # fill out defaults
1175 if not defaults:
1187 if not defaults:
1176 defaults = {}
1188 defaults = {}
1177 now = makedate()
1189 now = makedate()
1178 for part in "d mb yY HI M S".split():
1190 for part in "d mb yY HI M S".split():
1179 if part not in defaults:
1191 if part not in defaults:
1180 if part[0] in "HMS":
1192 if part[0] in "HMS":
1181 defaults[part] = "00"
1193 defaults[part] = "00"
1182 elif part[0] in "dm":
1194 elif part[0] in "dm":
1183 defaults[part] = "1"
1195 defaults[part] = "1"
1184 else:
1196 else:
1185 defaults[part] = datestr(now, "%" + part[0], False)
1197 defaults[part] = datestr(now, "%" + part[0], False)
1186
1198
1187 for format in formats:
1199 for format in formats:
1188 try:
1200 try:
1189 when, offset = strdate(string, format, defaults)
1201 when, offset = strdate(string, format, defaults)
1190 except ValueError:
1202 except ValueError:
1191 pass
1203 pass
1192 else:
1204 else:
1193 break
1205 break
1194 else:
1206 else:
1195 raise Abort(_('invalid date: %r ') % string)
1207 raise Abort(_('invalid date: %r ') % string)
1196 # validate explicit (probably user-specified) date and
1208 # validate explicit (probably user-specified) date and
1197 # time zone offset. values must fit in signed 32 bits for
1209 # time zone offset. values must fit in signed 32 bits for
1198 # current 32-bit linux runtimes. timezones go from UTC-12
1210 # current 32-bit linux runtimes. timezones go from UTC-12
1199 # to UTC+14
1211 # to UTC+14
1200 if abs(when) > 0x7fffffff:
1212 if abs(when) > 0x7fffffff:
1201 raise Abort(_('date exceeds 32 bits: %d') % when)
1213 raise Abort(_('date exceeds 32 bits: %d') % when)
1202 if offset < -50400 or offset > 43200:
1214 if offset < -50400 or offset > 43200:
1203 raise Abort(_('impossible time zone offset: %d') % offset)
1215 raise Abort(_('impossible time zone offset: %d') % offset)
1204 return when, offset
1216 return when, offset
1205
1217
1206 def matchdate(date):
1218 def matchdate(date):
1207 """Return a function that matches a given date match specifier
1219 """Return a function that matches a given date match specifier
1208
1220
1209 Formats include:
1221 Formats include:
1210
1222
1211 '{date}' match a given date to the accuracy provided
1223 '{date}' match a given date to the accuracy provided
1212
1224
1213 '<{date}' on or before a given date
1225 '<{date}' on or before a given date
1214
1226
1215 '>{date}' on or after a given date
1227 '>{date}' on or after a given date
1216
1228
1217 """
1229 """
1218
1230
1219 def lower(date):
1231 def lower(date):
1220 return parsedate(date, extendeddateformats)[0]
1232 return parsedate(date, extendeddateformats)[0]
1221
1233
1222 def upper(date):
1234 def upper(date):
1223 d = dict(mb="12", HI="23", M="59", S="59")
1235 d = dict(mb="12", HI="23", M="59", S="59")
1224 for days in "31 30 29".split():
1236 for days in "31 30 29".split():
1225 try:
1237 try:
1226 d["d"] = days
1238 d["d"] = days
1227 return parsedate(date, extendeddateformats, d)[0]
1239 return parsedate(date, extendeddateformats, d)[0]
1228 except:
1240 except:
1229 pass
1241 pass
1230 d["d"] = "28"
1242 d["d"] = "28"
1231 return parsedate(date, extendeddateformats, d)[0]
1243 return parsedate(date, extendeddateformats, d)[0]
1232
1244
1233 if date[0] == "<":
1245 if date[0] == "<":
1234 when = upper(date[1:])
1246 when = upper(date[1:])
1235 return lambda x: x <= when
1247 return lambda x: x <= when
1236 elif date[0] == ">":
1248 elif date[0] == ">":
1237 when = lower(date[1:])
1249 when = lower(date[1:])
1238 return lambda x: x >= when
1250 return lambda x: x >= when
1239 elif date[0] == "-":
1251 elif date[0] == "-":
1240 try:
1252 try:
1241 days = int(date[1:])
1253 days = int(date[1:])
1242 except ValueError:
1254 except ValueError:
1243 raise Abort(_("invalid day spec: %s") % date[1:])
1255 raise Abort(_("invalid day spec: %s") % date[1:])
1244 when = makedate()[0] - days * 3600 * 24
1256 when = makedate()[0] - days * 3600 * 24
1245 return lambda x: x >= when
1257 return lambda x: x >= when
1246 elif " to " in date:
1258 elif " to " in date:
1247 a, b = date.split(" to ")
1259 a, b = date.split(" to ")
1248 start, stop = lower(a), upper(b)
1260 start, stop = lower(a), upper(b)
1249 return lambda x: x >= start and x <= stop
1261 return lambda x: x >= start and x <= stop
1250 else:
1262 else:
1251 start, stop = lower(date), upper(date)
1263 start, stop = lower(date), upper(date)
1252 return lambda x: x >= start and x <= stop
1264 return lambda x: x >= start and x <= stop
1253
1265
1254 def shortuser(user):
1266 def shortuser(user):
1255 """Return a short representation of a user name or email address."""
1267 """Return a short representation of a user name or email address."""
1256 f = user.find('@')
1268 f = user.find('@')
1257 if f >= 0:
1269 if f >= 0:
1258 user = user[:f]
1270 user = user[:f]
1259 f = user.find('<')
1271 f = user.find('<')
1260 if f >= 0:
1272 if f >= 0:
1261 user = user[f+1:]
1273 user = user[f+1:]
1262 f = user.find(' ')
1274 f = user.find(' ')
1263 if f >= 0:
1275 if f >= 0:
1264 user = user[:f]
1276 user = user[:f]
1265 f = user.find('.')
1277 f = user.find('.')
1266 if f >= 0:
1278 if f >= 0:
1267 user = user[:f]
1279 user = user[:f]
1268 return user
1280 return user
1269
1281
1270 def ellipsis(text, maxlength=400):
1282 def ellipsis(text, maxlength=400):
1271 """Trim string to at most maxlength (default: 400) characters."""
1283 """Trim string to at most maxlength (default: 400) characters."""
1272 if len(text) <= maxlength:
1284 if len(text) <= maxlength:
1273 return text
1285 return text
1274 else:
1286 else:
1275 return "%s..." % (text[:maxlength-3])
1287 return "%s..." % (text[:maxlength-3])
1276
1288
1277 def walkrepos(path):
1289 def walkrepos(path):
1278 '''yield every hg repository under path, recursively.'''
1290 '''yield every hg repository under path, recursively.'''
1279 def errhandler(err):
1291 def errhandler(err):
1280 if err.filename == path:
1292 if err.filename == path:
1281 raise err
1293 raise err
1282
1294
1283 for root, dirs, files in os.walk(path, onerror=errhandler):
1295 for root, dirs, files in os.walk(path, onerror=errhandler):
1284 for d in dirs:
1296 for d in dirs:
1285 if d == '.hg':
1297 if d == '.hg':
1286 yield root
1298 yield root
1287 dirs[:] = []
1299 dirs[:] = []
1288 break
1300 break
1289
1301
1290 _rcpath = None
1302 _rcpath = None
1291
1303
1292 def rcpath():
1304 def rcpath():
1293 '''return hgrc search path. if env var HGRCPATH is set, use it.
1305 '''return hgrc search path. if env var HGRCPATH is set, use it.
1294 for each item in path, if directory, use files ending in .rc,
1306 for each item in path, if directory, use files ending in .rc,
1295 else use item.
1307 else use item.
1296 make HGRCPATH empty to only look in .hg/hgrc of current repo.
1308 make HGRCPATH empty to only look in .hg/hgrc of current repo.
1297 if no HGRCPATH, use default os-specific path.'''
1309 if no HGRCPATH, use default os-specific path.'''
1298 global _rcpath
1310 global _rcpath
1299 if _rcpath is None:
1311 if _rcpath is None:
1300 if 'HGRCPATH' in os.environ:
1312 if 'HGRCPATH' in os.environ:
1301 _rcpath = []
1313 _rcpath = []
1302 for p in os.environ['HGRCPATH'].split(os.pathsep):
1314 for p in os.environ['HGRCPATH'].split(os.pathsep):
1303 if not p: continue
1315 if not p: continue
1304 if os.path.isdir(p):
1316 if os.path.isdir(p):
1305 for f in os.listdir(p):
1317 for f in os.listdir(p):
1306 if f.endswith('.rc'):
1318 if f.endswith('.rc'):
1307 _rcpath.append(os.path.join(p, f))
1319 _rcpath.append(os.path.join(p, f))
1308 else:
1320 else:
1309 _rcpath.append(p)
1321 _rcpath.append(p)
1310 else:
1322 else:
1311 _rcpath = os_rcpath()
1323 _rcpath = os_rcpath()
1312 return _rcpath
1324 return _rcpath
1313
1325
1314 def bytecount(nbytes):
1326 def bytecount(nbytes):
1315 '''return byte count formatted as readable string, with units'''
1327 '''return byte count formatted as readable string, with units'''
1316
1328
1317 units = (
1329 units = (
1318 (100, 1<<30, _('%.0f GB')),
1330 (100, 1<<30, _('%.0f GB')),
1319 (10, 1<<30, _('%.1f GB')),
1331 (10, 1<<30, _('%.1f GB')),
1320 (1, 1<<30, _('%.2f GB')),
1332 (1, 1<<30, _('%.2f GB')),
1321 (100, 1<<20, _('%.0f MB')),
1333 (100, 1<<20, _('%.0f MB')),
1322 (10, 1<<20, _('%.1f MB')),
1334 (10, 1<<20, _('%.1f MB')),
1323 (1, 1<<20, _('%.2f MB')),
1335 (1, 1<<20, _('%.2f MB')),
1324 (100, 1<<10, _('%.0f KB')),
1336 (100, 1<<10, _('%.0f KB')),
1325 (10, 1<<10, _('%.1f KB')),
1337 (10, 1<<10, _('%.1f KB')),
1326 (1, 1<<10, _('%.2f KB')),
1338 (1, 1<<10, _('%.2f KB')),
1327 (1, 1, _('%.0f bytes')),
1339 (1, 1, _('%.0f bytes')),
1328 )
1340 )
1329
1341
1330 for multiplier, divisor, format in units:
1342 for multiplier, divisor, format in units:
1331 if nbytes >= divisor * multiplier:
1343 if nbytes >= divisor * multiplier:
1332 return format % (nbytes / float(divisor))
1344 return format % (nbytes / float(divisor))
1333 return units[-1][2] % nbytes
1345 return units[-1][2] % nbytes
1334
1346
1335 def drop_scheme(scheme, path):
1347 def drop_scheme(scheme, path):
1336 sc = scheme + ':'
1348 sc = scheme + ':'
1337 if path.startswith(sc):
1349 if path.startswith(sc):
1338 path = path[len(sc):]
1350 path = path[len(sc):]
1339 if path.startswith('//'):
1351 if path.startswith('//'):
1340 path = path[2:]
1352 path = path[2:]
1341 return path
1353 return path
General Comments 0
You need to be logged in to leave comments. Login now