##// END OF EJS Templates
util.strdate: assume local time when no timezone specified
Jose M. Prieto -
r3256:e5c9a084 default
parent child Browse files
Show More
@@ -1,1022 +1,1028
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")
18 demandload(globals(), "os threading time calendar")
19
19
20 # used by parsedate
20 # used by parsedate
21 defaultdateformats = ('%Y-%m-%d %H:%M:%S', '%Y-%m-%d %H:%M',
21 defaultdateformats = ('%Y-%m-%d %H:%M:%S', '%Y-%m-%d %H:%M',
22 '%a %b %d %H:%M:%S %Y')
22 '%a %b %d %H:%M:%S %Y')
23
23
24 class SignalInterrupt(Exception):
24 class SignalInterrupt(Exception):
25 """Exception raised on SIGTERM and SIGHUP."""
25 """Exception raised on SIGTERM and SIGHUP."""
26
26
27 def cachefunc(func):
27 def cachefunc(func):
28 '''cache the result of function calls'''
28 '''cache the result of function calls'''
29 # XXX doesn't handle keywords args
29 # XXX doesn't handle keywords args
30 cache = {}
30 cache = {}
31 if func.func_code.co_argcount == 1:
31 if func.func_code.co_argcount == 1:
32 # we gain a small amount of time because
32 # we gain a small amount of time because
33 # we don't need to pack/unpack the list
33 # we don't need to pack/unpack the list
34 def f(arg):
34 def f(arg):
35 if arg not in cache:
35 if arg not in cache:
36 cache[arg] = func(arg)
36 cache[arg] = func(arg)
37 return cache[arg]
37 return cache[arg]
38 else:
38 else:
39 def f(*args):
39 def f(*args):
40 if args not in cache:
40 if args not in cache:
41 cache[args] = func(*args)
41 cache[args] = func(*args)
42 return cache[args]
42 return cache[args]
43
43
44 return f
44 return f
45
45
46 def pipefilter(s, cmd):
46 def pipefilter(s, cmd):
47 '''filter string S through command CMD, returning its output'''
47 '''filter string S through command CMD, returning its output'''
48 (pout, pin) = popen2.popen2(cmd, -1, 'b')
48 (pout, pin) = popen2.popen2(cmd, -1, 'b')
49 def writer():
49 def writer():
50 try:
50 try:
51 pin.write(s)
51 pin.write(s)
52 pin.close()
52 pin.close()
53 except IOError, inst:
53 except IOError, inst:
54 if inst.errno != errno.EPIPE:
54 if inst.errno != errno.EPIPE:
55 raise
55 raise
56
56
57 # we should use select instead on UNIX, but this will work on most
57 # we should use select instead on UNIX, but this will work on most
58 # systems, including Windows
58 # systems, including Windows
59 w = threading.Thread(target=writer)
59 w = threading.Thread(target=writer)
60 w.start()
60 w.start()
61 f = pout.read()
61 f = pout.read()
62 pout.close()
62 pout.close()
63 w.join()
63 w.join()
64 return f
64 return f
65
65
66 def tempfilter(s, cmd):
66 def tempfilter(s, cmd):
67 '''filter string S through a pair of temporary files with CMD.
67 '''filter string S through a pair of temporary files with CMD.
68 CMD is used as a template to create the real command to be run,
68 CMD is used as a template to create the real command to be run,
69 with the strings INFILE and OUTFILE replaced by the real names of
69 with the strings INFILE and OUTFILE replaced by the real names of
70 the temporary files generated.'''
70 the temporary files generated.'''
71 inname, outname = None, None
71 inname, outname = None, None
72 try:
72 try:
73 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
73 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
74 fp = os.fdopen(infd, 'wb')
74 fp = os.fdopen(infd, 'wb')
75 fp.write(s)
75 fp.write(s)
76 fp.close()
76 fp.close()
77 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
77 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
78 os.close(outfd)
78 os.close(outfd)
79 cmd = cmd.replace('INFILE', inname)
79 cmd = cmd.replace('INFILE', inname)
80 cmd = cmd.replace('OUTFILE', outname)
80 cmd = cmd.replace('OUTFILE', outname)
81 code = os.system(cmd)
81 code = os.system(cmd)
82 if code: raise Abort(_("command '%s' failed: %s") %
82 if code: raise Abort(_("command '%s' failed: %s") %
83 (cmd, explain_exit(code)))
83 (cmd, explain_exit(code)))
84 return open(outname, 'rb').read()
84 return open(outname, 'rb').read()
85 finally:
85 finally:
86 try:
86 try:
87 if inname: os.unlink(inname)
87 if inname: os.unlink(inname)
88 except: pass
88 except: pass
89 try:
89 try:
90 if outname: os.unlink(outname)
90 if outname: os.unlink(outname)
91 except: pass
91 except: pass
92
92
93 filtertable = {
93 filtertable = {
94 'tempfile:': tempfilter,
94 'tempfile:': tempfilter,
95 'pipe:': pipefilter,
95 'pipe:': pipefilter,
96 }
96 }
97
97
98 def filter(s, cmd):
98 def filter(s, cmd):
99 "filter a string through a command that transforms its input to its output"
99 "filter a string through a command that transforms its input to its output"
100 for name, fn in filtertable.iteritems():
100 for name, fn in filtertable.iteritems():
101 if cmd.startswith(name):
101 if cmd.startswith(name):
102 return fn(s, cmd[len(name):].lstrip())
102 return fn(s, cmd[len(name):].lstrip())
103 return pipefilter(s, cmd)
103 return pipefilter(s, cmd)
104
104
105 def find_in_path(name, path, default=None):
105 def find_in_path(name, path, default=None):
106 '''find name in search path. path can be string (will be split
106 '''find name in search path. path can be string (will be split
107 with os.pathsep), or iterable thing that returns strings. if name
107 with os.pathsep), or iterable thing that returns strings. if name
108 found, return path to name. else return default.'''
108 found, return path to name. else return default.'''
109 if isinstance(path, str):
109 if isinstance(path, str):
110 path = path.split(os.pathsep)
110 path = path.split(os.pathsep)
111 for p in path:
111 for p in path:
112 p_name = os.path.join(p, name)
112 p_name = os.path.join(p, name)
113 if os.path.exists(p_name):
113 if os.path.exists(p_name):
114 return p_name
114 return p_name
115 return default
115 return default
116
116
117 def binary(s):
117 def binary(s):
118 """return true if a string is binary data using diff's heuristic"""
118 """return true if a string is binary data using diff's heuristic"""
119 if s and '\0' in s[:4096]:
119 if s and '\0' in s[:4096]:
120 return True
120 return True
121 return False
121 return False
122
122
123 def unique(g):
123 def unique(g):
124 """return the uniq elements of iterable g"""
124 """return the uniq elements of iterable g"""
125 seen = {}
125 seen = {}
126 for f in g:
126 for f in g:
127 if f not in seen:
127 if f not in seen:
128 seen[f] = 1
128 seen[f] = 1
129 yield f
129 yield f
130
130
131 class Abort(Exception):
131 class Abort(Exception):
132 """Raised if a command needs to print an error and exit."""
132 """Raised if a command needs to print an error and exit."""
133
133
134 def always(fn): return True
134 def always(fn): return True
135 def never(fn): return False
135 def never(fn): return False
136
136
137 def patkind(name, dflt_pat='glob'):
137 def patkind(name, dflt_pat='glob'):
138 """Split a string into an optional pattern kind prefix and the
138 """Split a string into an optional pattern kind prefix and the
139 actual pattern."""
139 actual pattern."""
140 for prefix in 're', 'glob', 'path', 'relglob', 'relpath', 'relre':
140 for prefix in 're', 'glob', 'path', 'relglob', 'relpath', 'relre':
141 if name.startswith(prefix + ':'): return name.split(':', 1)
141 if name.startswith(prefix + ':'): return name.split(':', 1)
142 return dflt_pat, name
142 return dflt_pat, name
143
143
144 def globre(pat, head='^', tail='$'):
144 def globre(pat, head='^', tail='$'):
145 "convert a glob pattern into a regexp"
145 "convert a glob pattern into a regexp"
146 i, n = 0, len(pat)
146 i, n = 0, len(pat)
147 res = ''
147 res = ''
148 group = False
148 group = False
149 def peek(): return i < n and pat[i]
149 def peek(): return i < n and pat[i]
150 while i < n:
150 while i < n:
151 c = pat[i]
151 c = pat[i]
152 i = i+1
152 i = i+1
153 if c == '*':
153 if c == '*':
154 if peek() == '*':
154 if peek() == '*':
155 i += 1
155 i += 1
156 res += '.*'
156 res += '.*'
157 else:
157 else:
158 res += '[^/]*'
158 res += '[^/]*'
159 elif c == '?':
159 elif c == '?':
160 res += '.'
160 res += '.'
161 elif c == '[':
161 elif c == '[':
162 j = i
162 j = i
163 if j < n and pat[j] in '!]':
163 if j < n and pat[j] in '!]':
164 j += 1
164 j += 1
165 while j < n and pat[j] != ']':
165 while j < n and pat[j] != ']':
166 j += 1
166 j += 1
167 if j >= n:
167 if j >= n:
168 res += '\\['
168 res += '\\['
169 else:
169 else:
170 stuff = pat[i:j].replace('\\','\\\\')
170 stuff = pat[i:j].replace('\\','\\\\')
171 i = j + 1
171 i = j + 1
172 if stuff[0] == '!':
172 if stuff[0] == '!':
173 stuff = '^' + stuff[1:]
173 stuff = '^' + stuff[1:]
174 elif stuff[0] == '^':
174 elif stuff[0] == '^':
175 stuff = '\\' + stuff
175 stuff = '\\' + stuff
176 res = '%s[%s]' % (res, stuff)
176 res = '%s[%s]' % (res, stuff)
177 elif c == '{':
177 elif c == '{':
178 group = True
178 group = True
179 res += '(?:'
179 res += '(?:'
180 elif c == '}' and group:
180 elif c == '}' and group:
181 res += ')'
181 res += ')'
182 group = False
182 group = False
183 elif c == ',' and group:
183 elif c == ',' and group:
184 res += '|'
184 res += '|'
185 elif c == '\\':
185 elif c == '\\':
186 p = peek()
186 p = peek()
187 if p:
187 if p:
188 i += 1
188 i += 1
189 res += re.escape(p)
189 res += re.escape(p)
190 else:
190 else:
191 res += re.escape(c)
191 res += re.escape(c)
192 else:
192 else:
193 res += re.escape(c)
193 res += re.escape(c)
194 return head + res + tail
194 return head + res + tail
195
195
196 _globchars = {'[': 1, '{': 1, '*': 1, '?': 1}
196 _globchars = {'[': 1, '{': 1, '*': 1, '?': 1}
197
197
198 def pathto(n1, n2):
198 def pathto(n1, n2):
199 '''return the relative path from one place to another.
199 '''return the relative path from one place to another.
200 this returns a path in the form used by the local filesystem, not hg.'''
200 this returns a path in the form used by the local filesystem, not hg.'''
201 if not n1: return localpath(n2)
201 if not n1: return localpath(n2)
202 a, b = n1.split('/'), n2.split('/')
202 a, b = n1.split('/'), n2.split('/')
203 a.reverse()
203 a.reverse()
204 b.reverse()
204 b.reverse()
205 while a and b and a[-1] == b[-1]:
205 while a and b and a[-1] == b[-1]:
206 a.pop()
206 a.pop()
207 b.pop()
207 b.pop()
208 b.reverse()
208 b.reverse()
209 return os.sep.join((['..'] * len(a)) + b)
209 return os.sep.join((['..'] * len(a)) + b)
210
210
211 def canonpath(root, cwd, myname):
211 def canonpath(root, cwd, myname):
212 """return the canonical path of myname, given cwd and root"""
212 """return the canonical path of myname, given cwd and root"""
213 if root == os.sep:
213 if root == os.sep:
214 rootsep = os.sep
214 rootsep = os.sep
215 elif root.endswith(os.sep):
215 elif root.endswith(os.sep):
216 rootsep = root
216 rootsep = root
217 else:
217 else:
218 rootsep = root + os.sep
218 rootsep = root + os.sep
219 name = myname
219 name = myname
220 if not os.path.isabs(name):
220 if not os.path.isabs(name):
221 name = os.path.join(root, cwd, name)
221 name = os.path.join(root, cwd, name)
222 name = os.path.normpath(name)
222 name = os.path.normpath(name)
223 if name != rootsep and name.startswith(rootsep):
223 if name != rootsep and name.startswith(rootsep):
224 name = name[len(rootsep):]
224 name = name[len(rootsep):]
225 audit_path(name)
225 audit_path(name)
226 return pconvert(name)
226 return pconvert(name)
227 elif name == root:
227 elif name == root:
228 return ''
228 return ''
229 else:
229 else:
230 # Determine whether `name' is in the hierarchy at or beneath `root',
230 # Determine whether `name' is in the hierarchy at or beneath `root',
231 # by iterating name=dirname(name) until that causes no change (can't
231 # by iterating name=dirname(name) until that causes no change (can't
232 # check name == '/', because that doesn't work on windows). For each
232 # check name == '/', because that doesn't work on windows). For each
233 # `name', compare dev/inode numbers. If they match, the list `rel'
233 # `name', compare dev/inode numbers. If they match, the list `rel'
234 # holds the reversed list of components making up the relative file
234 # holds the reversed list of components making up the relative file
235 # name we want.
235 # name we want.
236 root_st = os.stat(root)
236 root_st = os.stat(root)
237 rel = []
237 rel = []
238 while True:
238 while True:
239 try:
239 try:
240 name_st = os.stat(name)
240 name_st = os.stat(name)
241 except OSError:
241 except OSError:
242 break
242 break
243 if samestat(name_st, root_st):
243 if samestat(name_st, root_st):
244 rel.reverse()
244 rel.reverse()
245 name = os.path.join(*rel)
245 name = os.path.join(*rel)
246 audit_path(name)
246 audit_path(name)
247 return pconvert(name)
247 return pconvert(name)
248 dirname, basename = os.path.split(name)
248 dirname, basename = os.path.split(name)
249 rel.append(basename)
249 rel.append(basename)
250 if dirname == name:
250 if dirname == name:
251 break
251 break
252 name = dirname
252 name = dirname
253
253
254 raise Abort('%s not under root' % myname)
254 raise Abort('%s not under root' % myname)
255
255
256 def matcher(canonroot, cwd='', names=['.'], inc=[], exc=[], head='', src=None):
256 def matcher(canonroot, cwd='', names=['.'], inc=[], exc=[], head='', src=None):
257 return _matcher(canonroot, cwd, names, inc, exc, head, 'glob', src)
257 return _matcher(canonroot, cwd, names, inc, exc, head, 'glob', src)
258
258
259 def cmdmatcher(canonroot, cwd='', names=['.'], inc=[], exc=[], head='', src=None):
259 def cmdmatcher(canonroot, cwd='', names=['.'], inc=[], exc=[], head='', src=None):
260 if os.name == 'nt':
260 if os.name == 'nt':
261 dflt_pat = 'glob'
261 dflt_pat = 'glob'
262 else:
262 else:
263 dflt_pat = 'relpath'
263 dflt_pat = 'relpath'
264 return _matcher(canonroot, cwd, names, inc, exc, head, dflt_pat, src)
264 return _matcher(canonroot, cwd, names, inc, exc, head, dflt_pat, src)
265
265
266 def _matcher(canonroot, cwd, names, inc, exc, head, dflt_pat, src):
266 def _matcher(canonroot, cwd, names, inc, exc, head, dflt_pat, src):
267 """build a function to match a set of file patterns
267 """build a function to match a set of file patterns
268
268
269 arguments:
269 arguments:
270 canonroot - the canonical root of the tree you're matching against
270 canonroot - the canonical root of the tree you're matching against
271 cwd - the current working directory, if relevant
271 cwd - the current working directory, if relevant
272 names - patterns to find
272 names - patterns to find
273 inc - patterns to include
273 inc - patterns to include
274 exc - patterns to exclude
274 exc - patterns to exclude
275 head - a regex to prepend to patterns to control whether a match is rooted
275 head - a regex to prepend to patterns to control whether a match is rooted
276
276
277 a pattern is one of:
277 a pattern is one of:
278 'glob:<rooted glob>'
278 'glob:<rooted glob>'
279 're:<rooted regexp>'
279 're:<rooted regexp>'
280 'path:<rooted path>'
280 'path:<rooted path>'
281 'relglob:<relative glob>'
281 'relglob:<relative glob>'
282 'relpath:<relative path>'
282 'relpath:<relative path>'
283 'relre:<relative regexp>'
283 'relre:<relative regexp>'
284 '<rooted path or regexp>'
284 '<rooted path or regexp>'
285
285
286 returns:
286 returns:
287 a 3-tuple containing
287 a 3-tuple containing
288 - list of explicit non-pattern names passed in
288 - list of explicit non-pattern names passed in
289 - a bool match(filename) function
289 - a bool match(filename) function
290 - a bool indicating if any patterns were passed in
290 - a bool indicating if any patterns were passed in
291
291
292 todo:
292 todo:
293 make head regex a rooted bool
293 make head regex a rooted bool
294 """
294 """
295
295
296 def contains_glob(name):
296 def contains_glob(name):
297 for c in name:
297 for c in name:
298 if c in _globchars: return True
298 if c in _globchars: return True
299 return False
299 return False
300
300
301 def regex(kind, name, tail):
301 def regex(kind, name, tail):
302 '''convert a pattern into a regular expression'''
302 '''convert a pattern into a regular expression'''
303 if kind == 're':
303 if kind == 're':
304 return name
304 return name
305 elif kind == 'path':
305 elif kind == 'path':
306 return '^' + re.escape(name) + '(?:/|$)'
306 return '^' + re.escape(name) + '(?:/|$)'
307 elif kind == 'relglob':
307 elif kind == 'relglob':
308 return head + globre(name, '(?:|.*/)', tail)
308 return head + globre(name, '(?:|.*/)', tail)
309 elif kind == 'relpath':
309 elif kind == 'relpath':
310 return head + re.escape(name) + tail
310 return head + re.escape(name) + tail
311 elif kind == 'relre':
311 elif kind == 'relre':
312 if name.startswith('^'):
312 if name.startswith('^'):
313 return name
313 return name
314 return '.*' + name
314 return '.*' + name
315 return head + globre(name, '', tail)
315 return head + globre(name, '', tail)
316
316
317 def matchfn(pats, tail):
317 def matchfn(pats, tail):
318 """build a matching function from a set of patterns"""
318 """build a matching function from a set of patterns"""
319 if not pats:
319 if not pats:
320 return
320 return
321 matches = []
321 matches = []
322 for k, p in pats:
322 for k, p in pats:
323 try:
323 try:
324 pat = '(?:%s)' % regex(k, p, tail)
324 pat = '(?:%s)' % regex(k, p, tail)
325 matches.append(re.compile(pat).match)
325 matches.append(re.compile(pat).match)
326 except re.error:
326 except re.error:
327 if src: raise Abort("%s: invalid pattern (%s): %s" % (src, k, p))
327 if src: raise Abort("%s: invalid pattern (%s): %s" % (src, k, p))
328 else: raise Abort("invalid pattern (%s): %s" % (k, p))
328 else: raise Abort("invalid pattern (%s): %s" % (k, p))
329
329
330 def buildfn(text):
330 def buildfn(text):
331 for m in matches:
331 for m in matches:
332 r = m(text)
332 r = m(text)
333 if r:
333 if r:
334 return r
334 return r
335
335
336 return buildfn
336 return buildfn
337
337
338 def globprefix(pat):
338 def globprefix(pat):
339 '''return the non-glob prefix of a path, e.g. foo/* -> foo'''
339 '''return the non-glob prefix of a path, e.g. foo/* -> foo'''
340 root = []
340 root = []
341 for p in pat.split(os.sep):
341 for p in pat.split(os.sep):
342 if contains_glob(p): break
342 if contains_glob(p): break
343 root.append(p)
343 root.append(p)
344 return '/'.join(root)
344 return '/'.join(root)
345
345
346 pats = []
346 pats = []
347 files = []
347 files = []
348 roots = []
348 roots = []
349 for kind, name in [patkind(p, dflt_pat) for p in names]:
349 for kind, name in [patkind(p, dflt_pat) for p in names]:
350 if kind in ('glob', 'relpath'):
350 if kind in ('glob', 'relpath'):
351 name = canonpath(canonroot, cwd, name)
351 name = canonpath(canonroot, cwd, name)
352 if name == '':
352 if name == '':
353 kind, name = 'glob', '**'
353 kind, name = 'glob', '**'
354 if kind in ('glob', 'path', 're'):
354 if kind in ('glob', 'path', 're'):
355 pats.append((kind, name))
355 pats.append((kind, name))
356 if kind == 'glob':
356 if kind == 'glob':
357 root = globprefix(name)
357 root = globprefix(name)
358 if root: roots.append(root)
358 if root: roots.append(root)
359 elif kind == 'relpath':
359 elif kind == 'relpath':
360 files.append((kind, name))
360 files.append((kind, name))
361 roots.append(name)
361 roots.append(name)
362
362
363 patmatch = matchfn(pats, '$') or always
363 patmatch = matchfn(pats, '$') or always
364 filematch = matchfn(files, '(?:/|$)') or always
364 filematch = matchfn(files, '(?:/|$)') or always
365 incmatch = always
365 incmatch = always
366 if inc:
366 if inc:
367 inckinds = [patkind(canonpath(canonroot, cwd, i)) for i in inc]
367 inckinds = [patkind(canonpath(canonroot, cwd, i)) for i in inc]
368 incmatch = matchfn(inckinds, '(?:/|$)')
368 incmatch = matchfn(inckinds, '(?:/|$)')
369 excmatch = lambda fn: False
369 excmatch = lambda fn: False
370 if exc:
370 if exc:
371 exckinds = [patkind(canonpath(canonroot, cwd, x)) for x in exc]
371 exckinds = [patkind(canonpath(canonroot, cwd, x)) for x in exc]
372 excmatch = matchfn(exckinds, '(?:/|$)')
372 excmatch = matchfn(exckinds, '(?:/|$)')
373
373
374 return (roots,
374 return (roots,
375 lambda fn: (incmatch(fn) and not excmatch(fn) and
375 lambda fn: (incmatch(fn) and not excmatch(fn) and
376 (fn.endswith('/') or
376 (fn.endswith('/') or
377 (not pats and not files) or
377 (not pats and not files) or
378 (pats and patmatch(fn)) or
378 (pats and patmatch(fn)) or
379 (files and filematch(fn)))),
379 (files and filematch(fn)))),
380 (inc or exc or (pats and pats != [('glob', '**')])) and True)
380 (inc or exc or (pats and pats != [('glob', '**')])) and True)
381
381
382 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None):
382 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None):
383 '''enhanced shell command execution.
383 '''enhanced shell command execution.
384 run with environment maybe modified, maybe in different dir.
384 run with environment maybe modified, maybe in different dir.
385
385
386 if command fails and onerr is None, return status. if ui object,
386 if command fails and onerr is None, return status. if ui object,
387 print error message and return status, else raise onerr object as
387 print error message and return status, else raise onerr object as
388 exception.'''
388 exception.'''
389 def py2shell(val):
389 def py2shell(val):
390 'convert python object into string that is useful to shell'
390 'convert python object into string that is useful to shell'
391 if val in (None, False):
391 if val in (None, False):
392 return '0'
392 return '0'
393 if val == True:
393 if val == True:
394 return '1'
394 return '1'
395 return str(val)
395 return str(val)
396 oldenv = {}
396 oldenv = {}
397 for k in environ:
397 for k in environ:
398 oldenv[k] = os.environ.get(k)
398 oldenv[k] = os.environ.get(k)
399 if cwd is not None:
399 if cwd is not None:
400 oldcwd = os.getcwd()
400 oldcwd = os.getcwd()
401 try:
401 try:
402 for k, v in environ.iteritems():
402 for k, v in environ.iteritems():
403 os.environ[k] = py2shell(v)
403 os.environ[k] = py2shell(v)
404 if cwd is not None and oldcwd != cwd:
404 if cwd is not None and oldcwd != cwd:
405 os.chdir(cwd)
405 os.chdir(cwd)
406 rc = os.system(cmd)
406 rc = os.system(cmd)
407 if rc and onerr:
407 if rc and onerr:
408 errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]),
408 errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]),
409 explain_exit(rc)[0])
409 explain_exit(rc)[0])
410 if errprefix:
410 if errprefix:
411 errmsg = '%s: %s' % (errprefix, errmsg)
411 errmsg = '%s: %s' % (errprefix, errmsg)
412 try:
412 try:
413 onerr.warn(errmsg + '\n')
413 onerr.warn(errmsg + '\n')
414 except AttributeError:
414 except AttributeError:
415 raise onerr(errmsg)
415 raise onerr(errmsg)
416 return rc
416 return rc
417 finally:
417 finally:
418 for k, v in oldenv.iteritems():
418 for k, v in oldenv.iteritems():
419 if v is None:
419 if v is None:
420 del os.environ[k]
420 del os.environ[k]
421 else:
421 else:
422 os.environ[k] = v
422 os.environ[k] = v
423 if cwd is not None and oldcwd != cwd:
423 if cwd is not None and oldcwd != cwd:
424 os.chdir(oldcwd)
424 os.chdir(oldcwd)
425
425
426 def rename(src, dst):
426 def rename(src, dst):
427 """forcibly rename a file"""
427 """forcibly rename a file"""
428 try:
428 try:
429 os.rename(src, dst)
429 os.rename(src, dst)
430 except OSError, err:
430 except OSError, err:
431 # on windows, rename to existing file is not allowed, so we
431 # on windows, rename to existing file is not allowed, so we
432 # must delete destination first. but if file is open, unlink
432 # must delete destination first. but if file is open, unlink
433 # schedules it for delete but does not delete it. rename
433 # schedules it for delete but does not delete it. rename
434 # happens immediately even for open files, so we create
434 # happens immediately even for open files, so we create
435 # temporary file, delete it, rename destination to that name,
435 # temporary file, delete it, rename destination to that name,
436 # then delete that. then rename is safe to do.
436 # then delete that. then rename is safe to do.
437 fd, temp = tempfile.mkstemp(dir=os.path.dirname(dst) or '.')
437 fd, temp = tempfile.mkstemp(dir=os.path.dirname(dst) or '.')
438 os.close(fd)
438 os.close(fd)
439 os.unlink(temp)
439 os.unlink(temp)
440 os.rename(dst, temp)
440 os.rename(dst, temp)
441 os.unlink(temp)
441 os.unlink(temp)
442 os.rename(src, dst)
442 os.rename(src, dst)
443
443
444 def unlink(f):
444 def unlink(f):
445 """unlink and remove the directory if it is empty"""
445 """unlink and remove the directory if it is empty"""
446 os.unlink(f)
446 os.unlink(f)
447 # try removing directories that might now be empty
447 # try removing directories that might now be empty
448 try:
448 try:
449 os.removedirs(os.path.dirname(f))
449 os.removedirs(os.path.dirname(f))
450 except OSError:
450 except OSError:
451 pass
451 pass
452
452
453 def copyfiles(src, dst, hardlink=None):
453 def copyfiles(src, dst, hardlink=None):
454 """Copy a directory tree using hardlinks if possible"""
454 """Copy a directory tree using hardlinks if possible"""
455
455
456 if hardlink is None:
456 if hardlink is None:
457 hardlink = (os.stat(src).st_dev ==
457 hardlink = (os.stat(src).st_dev ==
458 os.stat(os.path.dirname(dst)).st_dev)
458 os.stat(os.path.dirname(dst)).st_dev)
459
459
460 if os.path.isdir(src):
460 if os.path.isdir(src):
461 os.mkdir(dst)
461 os.mkdir(dst)
462 for name in os.listdir(src):
462 for name in os.listdir(src):
463 srcname = os.path.join(src, name)
463 srcname = os.path.join(src, name)
464 dstname = os.path.join(dst, name)
464 dstname = os.path.join(dst, name)
465 copyfiles(srcname, dstname, hardlink)
465 copyfiles(srcname, dstname, hardlink)
466 else:
466 else:
467 if hardlink:
467 if hardlink:
468 try:
468 try:
469 os_link(src, dst)
469 os_link(src, dst)
470 except (IOError, OSError):
470 except (IOError, OSError):
471 hardlink = False
471 hardlink = False
472 shutil.copy(src, dst)
472 shutil.copy(src, dst)
473 else:
473 else:
474 shutil.copy(src, dst)
474 shutil.copy(src, dst)
475
475
476 def audit_path(path):
476 def audit_path(path):
477 """Abort if path contains dangerous components"""
477 """Abort if path contains dangerous components"""
478 parts = os.path.normcase(path).split(os.sep)
478 parts = os.path.normcase(path).split(os.sep)
479 if (os.path.splitdrive(path)[0] or parts[0] in ('.hg', '')
479 if (os.path.splitdrive(path)[0] or parts[0] in ('.hg', '')
480 or os.pardir in parts):
480 or os.pardir in parts):
481 raise Abort(_("path contains illegal component: %s\n") % path)
481 raise Abort(_("path contains illegal component: %s\n") % path)
482
482
483 def _makelock_file(info, pathname):
483 def _makelock_file(info, pathname):
484 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
484 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
485 os.write(ld, info)
485 os.write(ld, info)
486 os.close(ld)
486 os.close(ld)
487
487
488 def _readlock_file(pathname):
488 def _readlock_file(pathname):
489 return posixfile(pathname).read()
489 return posixfile(pathname).read()
490
490
491 def nlinks(pathname):
491 def nlinks(pathname):
492 """Return number of hardlinks for the given file."""
492 """Return number of hardlinks for the given file."""
493 return os.lstat(pathname).st_nlink
493 return os.lstat(pathname).st_nlink
494
494
495 if hasattr(os, 'link'):
495 if hasattr(os, 'link'):
496 os_link = os.link
496 os_link = os.link
497 else:
497 else:
498 def os_link(src, dst):
498 def os_link(src, dst):
499 raise OSError(0, _("Hardlinks not supported"))
499 raise OSError(0, _("Hardlinks not supported"))
500
500
501 def fstat(fp):
501 def fstat(fp):
502 '''stat file object that may not have fileno method.'''
502 '''stat file object that may not have fileno method.'''
503 try:
503 try:
504 return os.fstat(fp.fileno())
504 return os.fstat(fp.fileno())
505 except AttributeError:
505 except AttributeError:
506 return os.stat(fp.name)
506 return os.stat(fp.name)
507
507
508 posixfile = file
508 posixfile = file
509
509
510 def is_win_9x():
510 def is_win_9x():
511 '''return true if run on windows 95, 98 or me.'''
511 '''return true if run on windows 95, 98 or me.'''
512 try:
512 try:
513 return sys.getwindowsversion()[3] == 1
513 return sys.getwindowsversion()[3] == 1
514 except AttributeError:
514 except AttributeError:
515 return os.name == 'nt' and 'command' in os.environ.get('comspec', '')
515 return os.name == 'nt' and 'command' in os.environ.get('comspec', '')
516
516
517 getuser_fallback = None
517 getuser_fallback = None
518
518
519 def getuser():
519 def getuser():
520 '''return name of current user'''
520 '''return name of current user'''
521 try:
521 try:
522 return getpass.getuser()
522 return getpass.getuser()
523 except ImportError:
523 except ImportError:
524 # import of pwd will fail on windows - try fallback
524 # import of pwd will fail on windows - try fallback
525 if getuser_fallback:
525 if getuser_fallback:
526 return getuser_fallback()
526 return getuser_fallback()
527 # raised if win32api not available
527 # raised if win32api not available
528 raise Abort(_('user name not available - set USERNAME '
528 raise Abort(_('user name not available - set USERNAME '
529 'environment variable'))
529 'environment variable'))
530
530
531 # Platform specific variants
531 # Platform specific variants
532 if os.name == 'nt':
532 if os.name == 'nt':
533 demandload(globals(), "msvcrt")
533 demandload(globals(), "msvcrt")
534 nulldev = 'NUL:'
534 nulldev = 'NUL:'
535
535
536 class winstdout:
536 class winstdout:
537 '''stdout on windows misbehaves if sent through a pipe'''
537 '''stdout on windows misbehaves if sent through a pipe'''
538
538
539 def __init__(self, fp):
539 def __init__(self, fp):
540 self.fp = fp
540 self.fp = fp
541
541
542 def __getattr__(self, key):
542 def __getattr__(self, key):
543 return getattr(self.fp, key)
543 return getattr(self.fp, key)
544
544
545 def close(self):
545 def close(self):
546 try:
546 try:
547 self.fp.close()
547 self.fp.close()
548 except: pass
548 except: pass
549
549
550 def write(self, s):
550 def write(self, s):
551 try:
551 try:
552 return self.fp.write(s)
552 return self.fp.write(s)
553 except IOError, inst:
553 except IOError, inst:
554 if inst.errno != 0: raise
554 if inst.errno != 0: raise
555 self.close()
555 self.close()
556 raise IOError(errno.EPIPE, 'Broken pipe')
556 raise IOError(errno.EPIPE, 'Broken pipe')
557
557
558 sys.stdout = winstdout(sys.stdout)
558 sys.stdout = winstdout(sys.stdout)
559
559
560 def system_rcpath():
560 def system_rcpath():
561 try:
561 try:
562 return system_rcpath_win32()
562 return system_rcpath_win32()
563 except:
563 except:
564 return [r'c:\mercurial\mercurial.ini']
564 return [r'c:\mercurial\mercurial.ini']
565
565
566 def os_rcpath():
566 def os_rcpath():
567 '''return default os-specific hgrc search path'''
567 '''return default os-specific hgrc search path'''
568 path = system_rcpath()
568 path = system_rcpath()
569 path.append(user_rcpath())
569 path.append(user_rcpath())
570 userprofile = os.environ.get('USERPROFILE')
570 userprofile = os.environ.get('USERPROFILE')
571 if userprofile:
571 if userprofile:
572 path.append(os.path.join(userprofile, 'mercurial.ini'))
572 path.append(os.path.join(userprofile, 'mercurial.ini'))
573 return path
573 return path
574
574
575 def user_rcpath():
575 def user_rcpath():
576 '''return os-specific hgrc search path to the user dir'''
576 '''return os-specific hgrc search path to the user dir'''
577 return os.path.join(os.path.expanduser('~'), 'mercurial.ini')
577 return os.path.join(os.path.expanduser('~'), 'mercurial.ini')
578
578
579 def parse_patch_output(output_line):
579 def parse_patch_output(output_line):
580 """parses the output produced by patch and returns the file name"""
580 """parses the output produced by patch and returns the file name"""
581 pf = output_line[14:]
581 pf = output_line[14:]
582 if pf[0] == '`':
582 if pf[0] == '`':
583 pf = pf[1:-1] # Remove the quotes
583 pf = pf[1:-1] # Remove the quotes
584 return pf
584 return pf
585
585
586 def testpid(pid):
586 def testpid(pid):
587 '''return False if pid dead, True if running or not known'''
587 '''return False if pid dead, True if running or not known'''
588 return True
588 return True
589
589
590 def is_exec(f, last):
590 def is_exec(f, last):
591 return last
591 return last
592
592
593 def set_exec(f, mode):
593 def set_exec(f, mode):
594 pass
594 pass
595
595
596 def set_binary(fd):
596 def set_binary(fd):
597 msvcrt.setmode(fd.fileno(), os.O_BINARY)
597 msvcrt.setmode(fd.fileno(), os.O_BINARY)
598
598
599 def pconvert(path):
599 def pconvert(path):
600 return path.replace("\\", "/")
600 return path.replace("\\", "/")
601
601
602 def localpath(path):
602 def localpath(path):
603 return path.replace('/', '\\')
603 return path.replace('/', '\\')
604
604
605 def normpath(path):
605 def normpath(path):
606 return pconvert(os.path.normpath(path))
606 return pconvert(os.path.normpath(path))
607
607
608 makelock = _makelock_file
608 makelock = _makelock_file
609 readlock = _readlock_file
609 readlock = _readlock_file
610
610
611 def samestat(s1, s2):
611 def samestat(s1, s2):
612 return False
612 return False
613
613
614 def shellquote(s):
614 def shellquote(s):
615 return '"%s"' % s.replace('"', '\\"')
615 return '"%s"' % s.replace('"', '\\"')
616
616
617 def explain_exit(code):
617 def explain_exit(code):
618 return _("exited with status %d") % code, code
618 return _("exited with status %d") % code, code
619
619
620 try:
620 try:
621 # override functions with win32 versions if possible
621 # override functions with win32 versions if possible
622 from util_win32 import *
622 from util_win32 import *
623 if not is_win_9x():
623 if not is_win_9x():
624 posixfile = posixfile_nt
624 posixfile = posixfile_nt
625 except ImportError:
625 except ImportError:
626 pass
626 pass
627
627
628 else:
628 else:
629 nulldev = '/dev/null'
629 nulldev = '/dev/null'
630
630
631 def rcfiles(path):
631 def rcfiles(path):
632 rcs = [os.path.join(path, 'hgrc')]
632 rcs = [os.path.join(path, 'hgrc')]
633 rcdir = os.path.join(path, 'hgrc.d')
633 rcdir = os.path.join(path, 'hgrc.d')
634 try:
634 try:
635 rcs.extend([os.path.join(rcdir, f) for f in os.listdir(rcdir)
635 rcs.extend([os.path.join(rcdir, f) for f in os.listdir(rcdir)
636 if f.endswith(".rc")])
636 if f.endswith(".rc")])
637 except OSError:
637 except OSError:
638 pass
638 pass
639 return rcs
639 return rcs
640
640
641 def os_rcpath():
641 def os_rcpath():
642 '''return default os-specific hgrc search path'''
642 '''return default os-specific hgrc search path'''
643 path = []
643 path = []
644 # old mod_python does not set sys.argv
644 # old mod_python does not set sys.argv
645 if len(getattr(sys, 'argv', [])) > 0:
645 if len(getattr(sys, 'argv', [])) > 0:
646 path.extend(rcfiles(os.path.dirname(sys.argv[0]) +
646 path.extend(rcfiles(os.path.dirname(sys.argv[0]) +
647 '/../etc/mercurial'))
647 '/../etc/mercurial'))
648 path.extend(rcfiles('/etc/mercurial'))
648 path.extend(rcfiles('/etc/mercurial'))
649 path.append(os.path.expanduser('~/.hgrc'))
649 path.append(os.path.expanduser('~/.hgrc'))
650 path = [os.path.normpath(f) for f in path]
650 path = [os.path.normpath(f) for f in path]
651 return path
651 return path
652
652
653 def parse_patch_output(output_line):
653 def parse_patch_output(output_line):
654 """parses the output produced by patch and returns the file name"""
654 """parses the output produced by patch and returns the file name"""
655 pf = output_line[14:]
655 pf = output_line[14:]
656 if pf.startswith("'") and pf.endswith("'") and " " in pf:
656 if pf.startswith("'") and pf.endswith("'") and " " in pf:
657 pf = pf[1:-1] # Remove the quotes
657 pf = pf[1:-1] # Remove the quotes
658 return pf
658 return pf
659
659
660 def is_exec(f, last):
660 def is_exec(f, last):
661 """check whether a file is executable"""
661 """check whether a file is executable"""
662 return (os.lstat(f).st_mode & 0100 != 0)
662 return (os.lstat(f).st_mode & 0100 != 0)
663
663
664 def set_exec(f, mode):
664 def set_exec(f, mode):
665 s = os.lstat(f).st_mode
665 s = os.lstat(f).st_mode
666 if (s & 0100 != 0) == mode:
666 if (s & 0100 != 0) == mode:
667 return
667 return
668 if mode:
668 if mode:
669 # Turn on +x for every +r bit when making a file executable
669 # Turn on +x for every +r bit when making a file executable
670 # and obey umask.
670 # and obey umask.
671 umask = os.umask(0)
671 umask = os.umask(0)
672 os.umask(umask)
672 os.umask(umask)
673 os.chmod(f, s | (s & 0444) >> 2 & ~umask)
673 os.chmod(f, s | (s & 0444) >> 2 & ~umask)
674 else:
674 else:
675 os.chmod(f, s & 0666)
675 os.chmod(f, s & 0666)
676
676
677 def set_binary(fd):
677 def set_binary(fd):
678 pass
678 pass
679
679
680 def pconvert(path):
680 def pconvert(path):
681 return path
681 return path
682
682
683 def localpath(path):
683 def localpath(path):
684 return path
684 return path
685
685
686 normpath = os.path.normpath
686 normpath = os.path.normpath
687 samestat = os.path.samestat
687 samestat = os.path.samestat
688
688
689 def makelock(info, pathname):
689 def makelock(info, pathname):
690 try:
690 try:
691 os.symlink(info, pathname)
691 os.symlink(info, pathname)
692 except OSError, why:
692 except OSError, why:
693 if why.errno == errno.EEXIST:
693 if why.errno == errno.EEXIST:
694 raise
694 raise
695 else:
695 else:
696 _makelock_file(info, pathname)
696 _makelock_file(info, pathname)
697
697
698 def readlock(pathname):
698 def readlock(pathname):
699 try:
699 try:
700 return os.readlink(pathname)
700 return os.readlink(pathname)
701 except OSError, why:
701 except OSError, why:
702 if why.errno == errno.EINVAL:
702 if why.errno == errno.EINVAL:
703 return _readlock_file(pathname)
703 return _readlock_file(pathname)
704 else:
704 else:
705 raise
705 raise
706
706
707 def shellquote(s):
707 def shellquote(s):
708 return "'%s'" % s.replace("'", "'\\''")
708 return "'%s'" % s.replace("'", "'\\''")
709
709
710 def testpid(pid):
710 def testpid(pid):
711 '''return False if pid dead, True if running or not sure'''
711 '''return False if pid dead, True if running or not sure'''
712 try:
712 try:
713 os.kill(pid, 0)
713 os.kill(pid, 0)
714 return True
714 return True
715 except OSError, inst:
715 except OSError, inst:
716 return inst.errno != errno.ESRCH
716 return inst.errno != errno.ESRCH
717
717
718 def explain_exit(code):
718 def explain_exit(code):
719 """return a 2-tuple (desc, code) describing a process's status"""
719 """return a 2-tuple (desc, code) describing a process's status"""
720 if os.WIFEXITED(code):
720 if os.WIFEXITED(code):
721 val = os.WEXITSTATUS(code)
721 val = os.WEXITSTATUS(code)
722 return _("exited with status %d") % val, val
722 return _("exited with status %d") % val, val
723 elif os.WIFSIGNALED(code):
723 elif os.WIFSIGNALED(code):
724 val = os.WTERMSIG(code)
724 val = os.WTERMSIG(code)
725 return _("killed by signal %d") % val, val
725 return _("killed by signal %d") % val, val
726 elif os.WIFSTOPPED(code):
726 elif os.WIFSTOPPED(code):
727 val = os.WSTOPSIG(code)
727 val = os.WSTOPSIG(code)
728 return _("stopped by signal %d") % val, val
728 return _("stopped by signal %d") % val, val
729 raise ValueError(_("invalid exit code"))
729 raise ValueError(_("invalid exit code"))
730
730
731 def opener(base, audit=True):
731 def opener(base, audit=True):
732 """
732 """
733 return a function that opens files relative to base
733 return a function that opens files relative to base
734
734
735 this function is used to hide the details of COW semantics and
735 this function is used to hide the details of COW semantics and
736 remote file access from higher level code.
736 remote file access from higher level code.
737 """
737 """
738 p = base
738 p = base
739 audit_p = audit
739 audit_p = audit
740
740
741 def mktempcopy(name):
741 def mktempcopy(name):
742 d, fn = os.path.split(name)
742 d, fn = os.path.split(name)
743 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
743 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
744 os.close(fd)
744 os.close(fd)
745 ofp = posixfile(temp, "wb")
745 ofp = posixfile(temp, "wb")
746 try:
746 try:
747 try:
747 try:
748 ifp = posixfile(name, "rb")
748 ifp = posixfile(name, "rb")
749 except IOError, inst:
749 except IOError, inst:
750 if not getattr(inst, 'filename', None):
750 if not getattr(inst, 'filename', None):
751 inst.filename = name
751 inst.filename = name
752 raise
752 raise
753 for chunk in filechunkiter(ifp):
753 for chunk in filechunkiter(ifp):
754 ofp.write(chunk)
754 ofp.write(chunk)
755 ifp.close()
755 ifp.close()
756 ofp.close()
756 ofp.close()
757 except:
757 except:
758 try: os.unlink(temp)
758 try: os.unlink(temp)
759 except: pass
759 except: pass
760 raise
760 raise
761 st = os.lstat(name)
761 st = os.lstat(name)
762 os.chmod(temp, st.st_mode)
762 os.chmod(temp, st.st_mode)
763 return temp
763 return temp
764
764
765 class atomictempfile(posixfile):
765 class atomictempfile(posixfile):
766 """the file will only be copied when rename is called"""
766 """the file will only be copied when rename is called"""
767 def __init__(self, name, mode):
767 def __init__(self, name, mode):
768 self.__name = name
768 self.__name = name
769 self.temp = mktempcopy(name)
769 self.temp = mktempcopy(name)
770 posixfile.__init__(self, self.temp, mode)
770 posixfile.__init__(self, self.temp, mode)
771 def rename(self):
771 def rename(self):
772 if not self.closed:
772 if not self.closed:
773 posixfile.close(self)
773 posixfile.close(self)
774 rename(self.temp, localpath(self.__name))
774 rename(self.temp, localpath(self.__name))
775 def __del__(self):
775 def __del__(self):
776 if not self.closed:
776 if not self.closed:
777 try:
777 try:
778 os.unlink(self.temp)
778 os.unlink(self.temp)
779 except: pass
779 except: pass
780 posixfile.close(self)
780 posixfile.close(self)
781
781
782 class atomicfile(atomictempfile):
782 class atomicfile(atomictempfile):
783 """the file will only be copied on close"""
783 """the file will only be copied on close"""
784 def __init__(self, name, mode):
784 def __init__(self, name, mode):
785 atomictempfile.__init__(self, name, mode)
785 atomictempfile.__init__(self, name, mode)
786 def close(self):
786 def close(self):
787 self.rename()
787 self.rename()
788 def __del__(self):
788 def __del__(self):
789 self.rename()
789 self.rename()
790
790
791 def o(path, mode="r", text=False, atomic=False, atomictemp=False):
791 def o(path, mode="r", text=False, atomic=False, atomictemp=False):
792 if audit_p:
792 if audit_p:
793 audit_path(path)
793 audit_path(path)
794 f = os.path.join(p, path)
794 f = os.path.join(p, path)
795
795
796 if not text:
796 if not text:
797 mode += "b" # for that other OS
797 mode += "b" # for that other OS
798
798
799 if mode[0] != "r":
799 if mode[0] != "r":
800 try:
800 try:
801 nlink = nlinks(f)
801 nlink = nlinks(f)
802 except OSError:
802 except OSError:
803 d = os.path.dirname(f)
803 d = os.path.dirname(f)
804 if not os.path.isdir(d):
804 if not os.path.isdir(d):
805 os.makedirs(d)
805 os.makedirs(d)
806 else:
806 else:
807 if atomic:
807 if atomic:
808 return atomicfile(f, mode)
808 return atomicfile(f, mode)
809 elif atomictemp:
809 elif atomictemp:
810 return atomictempfile(f, mode)
810 return atomictempfile(f, mode)
811 if nlink > 1:
811 if nlink > 1:
812 rename(mktempcopy(f), f)
812 rename(mktempcopy(f), f)
813 return posixfile(f, mode)
813 return posixfile(f, mode)
814
814
815 return o
815 return o
816
816
817 class chunkbuffer(object):
817 class chunkbuffer(object):
818 """Allow arbitrary sized chunks of data to be efficiently read from an
818 """Allow arbitrary sized chunks of data to be efficiently read from an
819 iterator over chunks of arbitrary size."""
819 iterator over chunks of arbitrary size."""
820
820
821 def __init__(self, in_iter, targetsize = 2**16):
821 def __init__(self, in_iter, targetsize = 2**16):
822 """in_iter is the iterator that's iterating over the input chunks.
822 """in_iter is the iterator that's iterating over the input chunks.
823 targetsize is how big a buffer to try to maintain."""
823 targetsize is how big a buffer to try to maintain."""
824 self.in_iter = iter(in_iter)
824 self.in_iter = iter(in_iter)
825 self.buf = ''
825 self.buf = ''
826 self.targetsize = int(targetsize)
826 self.targetsize = int(targetsize)
827 if self.targetsize <= 0:
827 if self.targetsize <= 0:
828 raise ValueError(_("targetsize must be greater than 0, was %d") %
828 raise ValueError(_("targetsize must be greater than 0, was %d") %
829 targetsize)
829 targetsize)
830 self.iterempty = False
830 self.iterempty = False
831
831
832 def fillbuf(self):
832 def fillbuf(self):
833 """Ignore target size; read every chunk from iterator until empty."""
833 """Ignore target size; read every chunk from iterator until empty."""
834 if not self.iterempty:
834 if not self.iterempty:
835 collector = cStringIO.StringIO()
835 collector = cStringIO.StringIO()
836 collector.write(self.buf)
836 collector.write(self.buf)
837 for ch in self.in_iter:
837 for ch in self.in_iter:
838 collector.write(ch)
838 collector.write(ch)
839 self.buf = collector.getvalue()
839 self.buf = collector.getvalue()
840 self.iterempty = True
840 self.iterempty = True
841
841
842 def read(self, l):
842 def read(self, l):
843 """Read L bytes of data from the iterator of chunks of data.
843 """Read L bytes of data from the iterator of chunks of data.
844 Returns less than L bytes if the iterator runs dry."""
844 Returns less than L bytes if the iterator runs dry."""
845 if l > len(self.buf) and not self.iterempty:
845 if l > len(self.buf) and not self.iterempty:
846 # Clamp to a multiple of self.targetsize
846 # Clamp to a multiple of self.targetsize
847 targetsize = self.targetsize * ((l // self.targetsize) + 1)
847 targetsize = self.targetsize * ((l // self.targetsize) + 1)
848 collector = cStringIO.StringIO()
848 collector = cStringIO.StringIO()
849 collector.write(self.buf)
849 collector.write(self.buf)
850 collected = len(self.buf)
850 collected = len(self.buf)
851 for chunk in self.in_iter:
851 for chunk in self.in_iter:
852 collector.write(chunk)
852 collector.write(chunk)
853 collected += len(chunk)
853 collected += len(chunk)
854 if collected >= targetsize:
854 if collected >= targetsize:
855 break
855 break
856 if collected < targetsize:
856 if collected < targetsize:
857 self.iterempty = True
857 self.iterempty = True
858 self.buf = collector.getvalue()
858 self.buf = collector.getvalue()
859 s, self.buf = self.buf[:l], buffer(self.buf, l)
859 s, self.buf = self.buf[:l], buffer(self.buf, l)
860 return s
860 return s
861
861
862 def filechunkiter(f, size=65536, limit=None):
862 def filechunkiter(f, size=65536, limit=None):
863 """Create a generator that produces the data in the file size
863 """Create a generator that produces the data in the file size
864 (default 65536) bytes at a time, up to optional limit (default is
864 (default 65536) bytes at a time, up to optional limit (default is
865 to read all data). Chunks may be less than size bytes if the
865 to read all data). Chunks may be less than size bytes if the
866 chunk is the last chunk in the file, or the file is a socket or
866 chunk is the last chunk in the file, or the file is a socket or
867 some other type of file that sometimes reads less data than is
867 some other type of file that sometimes reads less data than is
868 requested."""
868 requested."""
869 assert size >= 0
869 assert size >= 0
870 assert limit is None or limit >= 0
870 assert limit is None or limit >= 0
871 while True:
871 while True:
872 if limit is None: nbytes = size
872 if limit is None: nbytes = size
873 else: nbytes = min(limit, size)
873 else: nbytes = min(limit, size)
874 s = nbytes and f.read(nbytes)
874 s = nbytes and f.read(nbytes)
875 if not s: break
875 if not s: break
876 if limit: limit -= len(s)
876 if limit: limit -= len(s)
877 yield s
877 yield s
878
878
879 def makedate():
879 def makedate():
880 lt = time.localtime()
880 lt = time.localtime()
881 if lt[8] == 1 and time.daylight:
881 if lt[8] == 1 and time.daylight:
882 tz = time.altzone
882 tz = time.altzone
883 else:
883 else:
884 tz = time.timezone
884 tz = time.timezone
885 return time.mktime(lt), tz
885 return time.mktime(lt), tz
886
886
887 def datestr(date=None, format='%a %b %d %H:%M:%S %Y', timezone=True):
887 def datestr(date=None, format='%a %b %d %H:%M:%S %Y', timezone=True):
888 """represent a (unixtime, offset) tuple as a localized time.
888 """represent a (unixtime, offset) tuple as a localized time.
889 unixtime is seconds since the epoch, and offset is the time zone's
889 unixtime is seconds since the epoch, and offset is the time zone's
890 number of seconds away from UTC. if timezone is false, do not
890 number of seconds away from UTC. if timezone is false, do not
891 append time zone to string."""
891 append time zone to string."""
892 t, tz = date or makedate()
892 t, tz = date or makedate()
893 s = time.strftime(format, time.gmtime(float(t) - tz))
893 s = time.strftime(format, time.gmtime(float(t) - tz))
894 if timezone:
894 if timezone:
895 s += " %+03d%02d" % (-tz / 3600, ((-tz % 3600) / 60))
895 s += " %+03d%02d" % (-tz / 3600, ((-tz % 3600) / 60))
896 return s
896 return s
897
897
898 def strdate(string, format='%a %b %d %H:%M:%S %Y'):
898 def strdate(string, format='%a %b %d %H:%M:%S %Y'):
899 """parse a localized time string and return a (unixtime, offset) tuple.
899 """parse a localized time string and return a (unixtime, offset) tuple.
900 if the string cannot be parsed, ValueError is raised."""
900 if the string cannot be parsed, ValueError is raised."""
901 def hastimezone(string):
901 def hastimezone(string):
902 return (string[-4:].isdigit() and
902 return (string[-4:].isdigit() and
903 (string[-5] == '+' or string[-5] == '-') and
903 (string[-5] == '+' or string[-5] == '-') and
904 string[-6].isspace())
904 string[-6].isspace())
905
905
906 # NOTE: unixtime = localunixtime + offset
906 # NOTE: unixtime = localunixtime + offset
907 if hastimezone(string):
907 if hastimezone(string):
908 date, tz = string[:-6], string[-5:]
908 date, tz = string[:-6], string[-5:]
909 tz = int(tz)
909 tz = int(tz)
910 offset = - 3600 * (tz / 100) - 60 * (tz % 100)
910 offset = - 3600 * (tz / 100) - 60 * (tz % 100)
911 else:
911 else:
912 date, offset = string, 0
912 date, offset = string, None
913 localunixtime = int(calendar.timegm(time.strptime(date, format)))
913 timetuple = time.strptime(date, format)
914 unixtime = localunixtime + offset
914 localunixtime = int(calendar.timegm(timetuple))
915 if offset is None:
916 # local timezone
917 unixtime = int(time.mktime(timetuple))
918 offset = unixtime - localunixtime
919 else:
920 unixtime = localunixtime + offset
915 return unixtime, offset
921 return unixtime, offset
916
922
917 def parsedate(string, formats=None):
923 def parsedate(string, formats=None):
918 """parse a localized time string and return a (unixtime, offset) tuple.
924 """parse a localized time string and return a (unixtime, offset) tuple.
919 The date may be a "unixtime offset" string or in one of the specified
925 The date may be a "unixtime offset" string or in one of the specified
920 formats."""
926 formats."""
921 if not formats:
927 if not formats:
922 formats = defaultdateformats
928 formats = defaultdateformats
923 try:
929 try:
924 when, offset = map(int, string.split(' '))
930 when, offset = map(int, string.split(' '))
925 except ValueError:
931 except ValueError:
926 for format in formats:
932 for format in formats:
927 try:
933 try:
928 when, offset = strdate(string, format)
934 when, offset = strdate(string, format)
929 except ValueError:
935 except ValueError:
930 pass
936 pass
931 else:
937 else:
932 break
938 break
933 else:
939 else:
934 raise ValueError(_('invalid date: %r') % string)
940 raise ValueError(_('invalid date: %r') % string)
935 # validate explicit (probably user-specified) date and
941 # validate explicit (probably user-specified) date and
936 # time zone offset. values must fit in signed 32 bits for
942 # time zone offset. values must fit in signed 32 bits for
937 # current 32-bit linux runtimes. timezones go from UTC-12
943 # current 32-bit linux runtimes. timezones go from UTC-12
938 # to UTC+14
944 # to UTC+14
939 if abs(when) > 0x7fffffff:
945 if abs(when) > 0x7fffffff:
940 raise ValueError(_('date exceeds 32 bits: %d') % when)
946 raise ValueError(_('date exceeds 32 bits: %d') % when)
941 if offset < -50400 or offset > 43200:
947 if offset < -50400 or offset > 43200:
942 raise ValueError(_('impossible time zone offset: %d') % offset)
948 raise ValueError(_('impossible time zone offset: %d') % offset)
943 return when, offset
949 return when, offset
944
950
945 def shortuser(user):
951 def shortuser(user):
946 """Return a short representation of a user name or email address."""
952 """Return a short representation of a user name or email address."""
947 f = user.find('@')
953 f = user.find('@')
948 if f >= 0:
954 if f >= 0:
949 user = user[:f]
955 user = user[:f]
950 f = user.find('<')
956 f = user.find('<')
951 if f >= 0:
957 if f >= 0:
952 user = user[f+1:]
958 user = user[f+1:]
953 f = user.find(' ')
959 f = user.find(' ')
954 if f >= 0:
960 if f >= 0:
955 user = user[:f]
961 user = user[:f]
956 return user
962 return user
957
963
958 def walkrepos(path):
964 def walkrepos(path):
959 '''yield every hg repository under path, recursively.'''
965 '''yield every hg repository under path, recursively.'''
960 def errhandler(err):
966 def errhandler(err):
961 if err.filename == path:
967 if err.filename == path:
962 raise err
968 raise err
963
969
964 for root, dirs, files in os.walk(path, onerror=errhandler):
970 for root, dirs, files in os.walk(path, onerror=errhandler):
965 for d in dirs:
971 for d in dirs:
966 if d == '.hg':
972 if d == '.hg':
967 yield root
973 yield root
968 dirs[:] = []
974 dirs[:] = []
969 break
975 break
970
976
971 _rcpath = None
977 _rcpath = None
972
978
973 def rcpath():
979 def rcpath():
974 '''return hgrc search path. if env var HGRCPATH is set, use it.
980 '''return hgrc search path. if env var HGRCPATH is set, use it.
975 for each item in path, if directory, use files ending in .rc,
981 for each item in path, if directory, use files ending in .rc,
976 else use item.
982 else use item.
977 make HGRCPATH empty to only look in .hg/hgrc of current repo.
983 make HGRCPATH empty to only look in .hg/hgrc of current repo.
978 if no HGRCPATH, use default os-specific path.'''
984 if no HGRCPATH, use default os-specific path.'''
979 global _rcpath
985 global _rcpath
980 if _rcpath is None:
986 if _rcpath is None:
981 if 'HGRCPATH' in os.environ:
987 if 'HGRCPATH' in os.environ:
982 _rcpath = []
988 _rcpath = []
983 for p in os.environ['HGRCPATH'].split(os.pathsep):
989 for p in os.environ['HGRCPATH'].split(os.pathsep):
984 if not p: continue
990 if not p: continue
985 if os.path.isdir(p):
991 if os.path.isdir(p):
986 for f in os.listdir(p):
992 for f in os.listdir(p):
987 if f.endswith('.rc'):
993 if f.endswith('.rc'):
988 _rcpath.append(os.path.join(p, f))
994 _rcpath.append(os.path.join(p, f))
989 else:
995 else:
990 _rcpath.append(p)
996 _rcpath.append(p)
991 else:
997 else:
992 _rcpath = os_rcpath()
998 _rcpath = os_rcpath()
993 return _rcpath
999 return _rcpath
994
1000
995 def bytecount(nbytes):
1001 def bytecount(nbytes):
996 '''return byte count formatted as readable string, with units'''
1002 '''return byte count formatted as readable string, with units'''
997
1003
998 units = (
1004 units = (
999 (100, 1<<30, _('%.0f GB')),
1005 (100, 1<<30, _('%.0f GB')),
1000 (10, 1<<30, _('%.1f GB')),
1006 (10, 1<<30, _('%.1f GB')),
1001 (1, 1<<30, _('%.2f GB')),
1007 (1, 1<<30, _('%.2f GB')),
1002 (100, 1<<20, _('%.0f MB')),
1008 (100, 1<<20, _('%.0f MB')),
1003 (10, 1<<20, _('%.1f MB')),
1009 (10, 1<<20, _('%.1f MB')),
1004 (1, 1<<20, _('%.2f MB')),
1010 (1, 1<<20, _('%.2f MB')),
1005 (100, 1<<10, _('%.0f KB')),
1011 (100, 1<<10, _('%.0f KB')),
1006 (10, 1<<10, _('%.1f KB')),
1012 (10, 1<<10, _('%.1f KB')),
1007 (1, 1<<10, _('%.2f KB')),
1013 (1, 1<<10, _('%.2f KB')),
1008 (1, 1, _('%.0f bytes')),
1014 (1, 1, _('%.0f bytes')),
1009 )
1015 )
1010
1016
1011 for multiplier, divisor, format in units:
1017 for multiplier, divisor, format in units:
1012 if nbytes >= divisor * multiplier:
1018 if nbytes >= divisor * multiplier:
1013 return format % (nbytes / float(divisor))
1019 return format % (nbytes / float(divisor))
1014 return units[-1][2] % nbytes
1020 return units[-1][2] % nbytes
1015
1021
1016 def drop_scheme(scheme, path):
1022 def drop_scheme(scheme, path):
1017 sc = scheme + ':'
1023 sc = scheme + ':'
1018 if path.startswith(sc):
1024 if path.startswith(sc):
1019 path = path[len(sc):]
1025 path = path[len(sc):]
1020 if path.startswith('//'):
1026 if path.startswith('//'):
1021 path = path[2:]
1027 path = path[2:]
1022 return path
1028 return path
@@ -1,34 +1,34
1 #!/bin/sh
1 #!/bin/sh
2
2
3 # This runs with TZ="GMT"
3 # This runs with TZ="GMT"
4 hg init
4 hg init
5 echo "test-parse-date" > a
5 echo "test-parse-date" > a
6 hg add a
6 hg add a
7 hg ci -d "2006-02-01 13:00:30" -m "rev 0"
7 hg ci -d "2006-02-01 13:00:30" -m "rev 0"
8 echo "hi!" >> a
8 echo "hi!" >> a
9 hg ci -d "2006-02-01 13:00:30 -0500" -m "rev 1"
9 hg ci -d "2006-02-01 13:00:30 -0500" -m "rev 1"
10 hg tag -d "2006-04-15 13:30" "Hi"
10 hg tag -d "2006-04-15 13:30" "Hi"
11 hg backout --merge -d "2006-04-15 13:30 +0200" -m "rev 3" 1
11 hg backout --merge -d "2006-04-15 13:30 +0200" -m "rev 3" 1
12 hg ci -d "1150000000 14400" -m "rev 4 (merge)"
12 hg ci -d "1150000000 14400" -m "rev 4 (merge)"
13 echo "fail" >> a
13 echo "fail" >> a
14 hg ci -d "should fail" -m "fail"
14 hg ci -d "should fail" -m "fail"
15 hg ci -d "100000000000000000 1400" -m "fail"
15 hg ci -d "100000000000000000 1400" -m "fail"
16 hg ci -d "100000 1400000" -m "fail"
16 hg ci -d "100000 1400000" -m "fail"
17
17
18 # Check with local timezone other than GMT and with DST
18 # Check with local timezone other than GMT and with DST
19 TZ="PST+8PDT"
19 TZ="PST+8PDT"
20 export TZ
20 export TZ
21 # PST=UTC-8 / PDT=UTC-7
21 # PST=UTC-8 / PDT=UTC-7
22 hg debugrebuildstate
22 hg debugrebuildstate
23 echo "a" > a
23 echo "a" > a
24 hg ci -d "2006-07-15 13:30" -m "summer@UTC"
24 hg ci -d "2006-07-15 13:30" -m "summer@UTC-7"
25 hg debugrebuildstate
25 hg debugrebuildstate
26 echo "b" > a
26 echo "b" > a
27 hg ci -d "2006-07-15 13:30 +0500" -m "summer@UTC+5"
27 hg ci -d "2006-07-15 13:30 +0500" -m "summer@UTC+5"
28 hg debugrebuildstate
28 hg debugrebuildstate
29 echo "c" > a
29 echo "c" > a
30 hg ci -d "2006-01-15 13:30" -m "winter@UTC"
30 hg ci -d "2006-01-15 13:30" -m "winter@UTC-8"
31 hg debugrebuildstate
31 hg debugrebuildstate
32 echo "d" > a
32 echo "d" > a
33 hg ci -d "2006-01-15 13:30 +0500" -m "winter@UTC+5"
33 hg ci -d "2006-01-15 13:30 +0500" -m "winter@UTC+5"
34 hg log --template '{date|date}\n'
34 hg log --template '{date|date}\n'
@@ -1,23 +1,23
1 reverting a
1 reverting a
2 changeset 3:107ce1ee2b43 backs out changeset 1:25a1420a55f8
2 changeset 3:107ce1ee2b43 backs out changeset 1:25a1420a55f8
3 merging with changeset 2:e6c3abc120e7
3 merging with changeset 2:e6c3abc120e7
4 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
4 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
5 (branch merge, don't forget to commit)
5 (branch merge, don't forget to commit)
6 abort: invalid date: 'should fail'
6 abort: invalid date: 'should fail'
7 transaction abort!
7 transaction abort!
8 rollback completed
8 rollback completed
9 abort: date exceeds 32 bits: 100000000000000000
9 abort: date exceeds 32 bits: 100000000000000000
10 transaction abort!
10 transaction abort!
11 rollback completed
11 rollback completed
12 abort: impossible time zone offset: 1400000
12 abort: impossible time zone offset: 1400000
13 transaction abort!
13 transaction abort!
14 rollback completed
14 rollback completed
15 Sun Jan 15 13:30:00 2006 +0500
15 Sun Jan 15 13:30:00 2006 +0500
16 Sun Jan 15 13:30:00 2006 +0000
16 Sun Jan 15 13:30:00 2006 -0800
17 Sat Jul 15 13:30:00 2006 +0500
17 Sat Jul 15 13:30:00 2006 +0500
18 Sat Jul 15 13:30:00 2006 +0000
18 Sat Jul 15 13:30:00 2006 -0700
19 Sun Jun 11 00:26:40 2006 -0400
19 Sun Jun 11 00:26:40 2006 -0400
20 Sat Apr 15 13:30:00 2006 +0200
20 Sat Apr 15 13:30:00 2006 +0200
21 Sat Apr 15 13:30:00 2006 +0000
21 Sat Apr 15 13:30:00 2006 +0000
22 Wed Feb 01 13:00:30 2006 -0500
22 Wed Feb 01 13:00:30 2006 -0500
23 Wed Feb 01 13:00:30 2006 +0000
23 Wed Feb 01 13:00:30 2006 +0000
General Comments 0
You need to be logged in to leave comments. Login now