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