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