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