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