##// END OF EJS Templates
cleanup: replace more naked excepts with more specific ones
Brodie Rao -
r16703:7292a461 default
parent child Browse files
Show More
@@ -1,754 +1,754
1 # ui.py - user interface bits for mercurial
1 # ui.py - user interface bits for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from i18n import _
8 from i18n import _
9 import errno, getpass, os, socket, sys, tempfile, traceback
9 import errno, getpass, os, socket, sys, tempfile, traceback
10 import config, scmutil, util, error, formatter
10 import config, scmutil, util, error, formatter
11
11
12 class ui(object):
12 class ui(object):
13 def __init__(self, src=None):
13 def __init__(self, src=None):
14 self._buffers = []
14 self._buffers = []
15 self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
15 self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
16 self._reportuntrusted = True
16 self._reportuntrusted = True
17 self._ocfg = config.config() # overlay
17 self._ocfg = config.config() # overlay
18 self._tcfg = config.config() # trusted
18 self._tcfg = config.config() # trusted
19 self._ucfg = config.config() # untrusted
19 self._ucfg = config.config() # untrusted
20 self._trustusers = set()
20 self._trustusers = set()
21 self._trustgroups = set()
21 self._trustgroups = set()
22
22
23 if src:
23 if src:
24 self.fout = src.fout
24 self.fout = src.fout
25 self.ferr = src.ferr
25 self.ferr = src.ferr
26 self.fin = src.fin
26 self.fin = src.fin
27
27
28 self._tcfg = src._tcfg.copy()
28 self._tcfg = src._tcfg.copy()
29 self._ucfg = src._ucfg.copy()
29 self._ucfg = src._ucfg.copy()
30 self._ocfg = src._ocfg.copy()
30 self._ocfg = src._ocfg.copy()
31 self._trustusers = src._trustusers.copy()
31 self._trustusers = src._trustusers.copy()
32 self._trustgroups = src._trustgroups.copy()
32 self._trustgroups = src._trustgroups.copy()
33 self.environ = src.environ
33 self.environ = src.environ
34 self.fixconfig()
34 self.fixconfig()
35 else:
35 else:
36 self.fout = sys.stdout
36 self.fout = sys.stdout
37 self.ferr = sys.stderr
37 self.ferr = sys.stderr
38 self.fin = sys.stdin
38 self.fin = sys.stdin
39
39
40 # shared read-only environment
40 # shared read-only environment
41 self.environ = os.environ
41 self.environ = os.environ
42 # we always trust global config files
42 # we always trust global config files
43 for f in scmutil.rcpath():
43 for f in scmutil.rcpath():
44 self.readconfig(f, trust=True)
44 self.readconfig(f, trust=True)
45
45
46 def copy(self):
46 def copy(self):
47 return self.__class__(self)
47 return self.__class__(self)
48
48
49 def formatter(self, topic, opts):
49 def formatter(self, topic, opts):
50 return formatter.formatter(self, topic, opts)
50 return formatter.formatter(self, topic, opts)
51
51
52 def _trusted(self, fp, f):
52 def _trusted(self, fp, f):
53 st = util.fstat(fp)
53 st = util.fstat(fp)
54 if util.isowner(st):
54 if util.isowner(st):
55 return True
55 return True
56
56
57 tusers, tgroups = self._trustusers, self._trustgroups
57 tusers, tgroups = self._trustusers, self._trustgroups
58 if '*' in tusers or '*' in tgroups:
58 if '*' in tusers or '*' in tgroups:
59 return True
59 return True
60
60
61 user = util.username(st.st_uid)
61 user = util.username(st.st_uid)
62 group = util.groupname(st.st_gid)
62 group = util.groupname(st.st_gid)
63 if user in tusers or group in tgroups or user == util.username():
63 if user in tusers or group in tgroups or user == util.username():
64 return True
64 return True
65
65
66 if self._reportuntrusted:
66 if self._reportuntrusted:
67 self.warn(_('Not trusting file %s from untrusted '
67 self.warn(_('Not trusting file %s from untrusted '
68 'user %s, group %s\n') % (f, user, group))
68 'user %s, group %s\n') % (f, user, group))
69 return False
69 return False
70
70
71 def readconfig(self, filename, root=None, trust=False,
71 def readconfig(self, filename, root=None, trust=False,
72 sections=None, remap=None):
72 sections=None, remap=None):
73 try:
73 try:
74 fp = open(filename)
74 fp = open(filename)
75 except IOError:
75 except IOError:
76 if not sections: # ignore unless we were looking for something
76 if not sections: # ignore unless we were looking for something
77 return
77 return
78 raise
78 raise
79
79
80 cfg = config.config()
80 cfg = config.config()
81 trusted = sections or trust or self._trusted(fp, filename)
81 trusted = sections or trust or self._trusted(fp, filename)
82
82
83 try:
83 try:
84 cfg.read(filename, fp, sections=sections, remap=remap)
84 cfg.read(filename, fp, sections=sections, remap=remap)
85 fp.close()
85 fp.close()
86 except error.ConfigError, inst:
86 except error.ConfigError, inst:
87 if trusted:
87 if trusted:
88 raise
88 raise
89 self.warn(_("Ignored: %s\n") % str(inst))
89 self.warn(_("Ignored: %s\n") % str(inst))
90
90
91 if self.plain():
91 if self.plain():
92 for k in ('debug', 'fallbackencoding', 'quiet', 'slash',
92 for k in ('debug', 'fallbackencoding', 'quiet', 'slash',
93 'logtemplate', 'style',
93 'logtemplate', 'style',
94 'traceback', 'verbose'):
94 'traceback', 'verbose'):
95 if k in cfg['ui']:
95 if k in cfg['ui']:
96 del cfg['ui'][k]
96 del cfg['ui'][k]
97 for k, v in cfg.items('defaults'):
97 for k, v in cfg.items('defaults'):
98 del cfg['defaults'][k]
98 del cfg['defaults'][k]
99 # Don't remove aliases from the configuration if in the exceptionlist
99 # Don't remove aliases from the configuration if in the exceptionlist
100 if self.plain('alias'):
100 if self.plain('alias'):
101 for k, v in cfg.items('alias'):
101 for k, v in cfg.items('alias'):
102 del cfg['alias'][k]
102 del cfg['alias'][k]
103
103
104 if trusted:
104 if trusted:
105 self._tcfg.update(cfg)
105 self._tcfg.update(cfg)
106 self._tcfg.update(self._ocfg)
106 self._tcfg.update(self._ocfg)
107 self._ucfg.update(cfg)
107 self._ucfg.update(cfg)
108 self._ucfg.update(self._ocfg)
108 self._ucfg.update(self._ocfg)
109
109
110 if root is None:
110 if root is None:
111 root = os.path.expanduser('~')
111 root = os.path.expanduser('~')
112 self.fixconfig(root=root)
112 self.fixconfig(root=root)
113
113
114 def fixconfig(self, root=None, section=None):
114 def fixconfig(self, root=None, section=None):
115 if section in (None, 'paths'):
115 if section in (None, 'paths'):
116 # expand vars and ~
116 # expand vars and ~
117 # translate paths relative to root (or home) into absolute paths
117 # translate paths relative to root (or home) into absolute paths
118 root = root or os.getcwd()
118 root = root or os.getcwd()
119 for c in self._tcfg, self._ucfg, self._ocfg:
119 for c in self._tcfg, self._ucfg, self._ocfg:
120 for n, p in c.items('paths'):
120 for n, p in c.items('paths'):
121 if not p:
121 if not p:
122 continue
122 continue
123 if '%%' in p:
123 if '%%' in p:
124 self.warn(_("(deprecated '%%' in path %s=%s from %s)\n")
124 self.warn(_("(deprecated '%%' in path %s=%s from %s)\n")
125 % (n, p, self.configsource('paths', n)))
125 % (n, p, self.configsource('paths', n)))
126 p = p.replace('%%', '%')
126 p = p.replace('%%', '%')
127 p = util.expandpath(p)
127 p = util.expandpath(p)
128 if not util.hasscheme(p) and not os.path.isabs(p):
128 if not util.hasscheme(p) and not os.path.isabs(p):
129 p = os.path.normpath(os.path.join(root, p))
129 p = os.path.normpath(os.path.join(root, p))
130 c.set("paths", n, p)
130 c.set("paths", n, p)
131
131
132 if section in (None, 'ui'):
132 if section in (None, 'ui'):
133 # update ui options
133 # update ui options
134 self.debugflag = self.configbool('ui', 'debug')
134 self.debugflag = self.configbool('ui', 'debug')
135 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
135 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
136 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
136 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
137 if self.verbose and self.quiet:
137 if self.verbose and self.quiet:
138 self.quiet = self.verbose = False
138 self.quiet = self.verbose = False
139 self._reportuntrusted = self.debugflag or self.configbool("ui",
139 self._reportuntrusted = self.debugflag or self.configbool("ui",
140 "report_untrusted", True)
140 "report_untrusted", True)
141 self.tracebackflag = self.configbool('ui', 'traceback', False)
141 self.tracebackflag = self.configbool('ui', 'traceback', False)
142
142
143 if section in (None, 'trusted'):
143 if section in (None, 'trusted'):
144 # update trust information
144 # update trust information
145 self._trustusers.update(self.configlist('trusted', 'users'))
145 self._trustusers.update(self.configlist('trusted', 'users'))
146 self._trustgroups.update(self.configlist('trusted', 'groups'))
146 self._trustgroups.update(self.configlist('trusted', 'groups'))
147
147
148 def backupconfig(self, section, item):
148 def backupconfig(self, section, item):
149 return (self._ocfg.backup(section, item),
149 return (self._ocfg.backup(section, item),
150 self._tcfg.backup(section, item),
150 self._tcfg.backup(section, item),
151 self._ucfg.backup(section, item),)
151 self._ucfg.backup(section, item),)
152 def restoreconfig(self, data):
152 def restoreconfig(self, data):
153 self._ocfg.restore(data[0])
153 self._ocfg.restore(data[0])
154 self._tcfg.restore(data[1])
154 self._tcfg.restore(data[1])
155 self._ucfg.restore(data[2])
155 self._ucfg.restore(data[2])
156
156
157 def setconfig(self, section, name, value, overlay=True):
157 def setconfig(self, section, name, value, overlay=True):
158 if overlay:
158 if overlay:
159 self._ocfg.set(section, name, value)
159 self._ocfg.set(section, name, value)
160 self._tcfg.set(section, name, value)
160 self._tcfg.set(section, name, value)
161 self._ucfg.set(section, name, value)
161 self._ucfg.set(section, name, value)
162 self.fixconfig(section=section)
162 self.fixconfig(section=section)
163
163
164 def _data(self, untrusted):
164 def _data(self, untrusted):
165 return untrusted and self._ucfg or self._tcfg
165 return untrusted and self._ucfg or self._tcfg
166
166
167 def configsource(self, section, name, untrusted=False):
167 def configsource(self, section, name, untrusted=False):
168 return self._data(untrusted).source(section, name) or 'none'
168 return self._data(untrusted).source(section, name) or 'none'
169
169
170 def config(self, section, name, default=None, untrusted=False):
170 def config(self, section, name, default=None, untrusted=False):
171 if isinstance(name, list):
171 if isinstance(name, list):
172 alternates = name
172 alternates = name
173 else:
173 else:
174 alternates = [name]
174 alternates = [name]
175
175
176 for n in alternates:
176 for n in alternates:
177 value = self._data(untrusted).get(section, name, None)
177 value = self._data(untrusted).get(section, name, None)
178 if value is not None:
178 if value is not None:
179 name = n
179 name = n
180 break
180 break
181 else:
181 else:
182 value = default
182 value = default
183
183
184 if self.debugflag and not untrusted and self._reportuntrusted:
184 if self.debugflag and not untrusted and self._reportuntrusted:
185 uvalue = self._ucfg.get(section, name)
185 uvalue = self._ucfg.get(section, name)
186 if uvalue is not None and uvalue != value:
186 if uvalue is not None and uvalue != value:
187 self.debug("ignoring untrusted configuration option "
187 self.debug("ignoring untrusted configuration option "
188 "%s.%s = %s\n" % (section, name, uvalue))
188 "%s.%s = %s\n" % (section, name, uvalue))
189 return value
189 return value
190
190
191 def configpath(self, section, name, default=None, untrusted=False):
191 def configpath(self, section, name, default=None, untrusted=False):
192 'get a path config item, expanded relative to repo root or config file'
192 'get a path config item, expanded relative to repo root or config file'
193 v = self.config(section, name, default, untrusted)
193 v = self.config(section, name, default, untrusted)
194 if v is None:
194 if v is None:
195 return None
195 return None
196 if not os.path.isabs(v) or "://" not in v:
196 if not os.path.isabs(v) or "://" not in v:
197 src = self.configsource(section, name, untrusted)
197 src = self.configsource(section, name, untrusted)
198 if ':' in src:
198 if ':' in src:
199 base = os.path.dirname(src.rsplit(':')[0])
199 base = os.path.dirname(src.rsplit(':')[0])
200 v = os.path.join(base, os.path.expanduser(v))
200 v = os.path.join(base, os.path.expanduser(v))
201 return v
201 return v
202
202
203 def configbool(self, section, name, default=False, untrusted=False):
203 def configbool(self, section, name, default=False, untrusted=False):
204 """parse a configuration element as a boolean
204 """parse a configuration element as a boolean
205
205
206 >>> u = ui(); s = 'foo'
206 >>> u = ui(); s = 'foo'
207 >>> u.setconfig(s, 'true', 'yes')
207 >>> u.setconfig(s, 'true', 'yes')
208 >>> u.configbool(s, 'true')
208 >>> u.configbool(s, 'true')
209 True
209 True
210 >>> u.setconfig(s, 'false', 'no')
210 >>> u.setconfig(s, 'false', 'no')
211 >>> u.configbool(s, 'false')
211 >>> u.configbool(s, 'false')
212 False
212 False
213 >>> u.configbool(s, 'unknown')
213 >>> u.configbool(s, 'unknown')
214 False
214 False
215 >>> u.configbool(s, 'unknown', True)
215 >>> u.configbool(s, 'unknown', True)
216 True
216 True
217 >>> u.setconfig(s, 'invalid', 'somevalue')
217 >>> u.setconfig(s, 'invalid', 'somevalue')
218 >>> u.configbool(s, 'invalid')
218 >>> u.configbool(s, 'invalid')
219 Traceback (most recent call last):
219 Traceback (most recent call last):
220 ...
220 ...
221 ConfigError: foo.invalid is not a boolean ('somevalue')
221 ConfigError: foo.invalid is not a boolean ('somevalue')
222 """
222 """
223
223
224 v = self.config(section, name, None, untrusted)
224 v = self.config(section, name, None, untrusted)
225 if v is None:
225 if v is None:
226 return default
226 return default
227 if isinstance(v, bool):
227 if isinstance(v, bool):
228 return v
228 return v
229 b = util.parsebool(v)
229 b = util.parsebool(v)
230 if b is None:
230 if b is None:
231 raise error.ConfigError(_("%s.%s is not a boolean ('%s')")
231 raise error.ConfigError(_("%s.%s is not a boolean ('%s')")
232 % (section, name, v))
232 % (section, name, v))
233 return b
233 return b
234
234
235 def configint(self, section, name, default=None, untrusted=False):
235 def configint(self, section, name, default=None, untrusted=False):
236 """parse a configuration element as an integer
236 """parse a configuration element as an integer
237
237
238 >>> u = ui(); s = 'foo'
238 >>> u = ui(); s = 'foo'
239 >>> u.setconfig(s, 'int1', '42')
239 >>> u.setconfig(s, 'int1', '42')
240 >>> u.configint(s, 'int1')
240 >>> u.configint(s, 'int1')
241 42
241 42
242 >>> u.setconfig(s, 'int2', '-42')
242 >>> u.setconfig(s, 'int2', '-42')
243 >>> u.configint(s, 'int2')
243 >>> u.configint(s, 'int2')
244 -42
244 -42
245 >>> u.configint(s, 'unknown', 7)
245 >>> u.configint(s, 'unknown', 7)
246 7
246 7
247 >>> u.setconfig(s, 'invalid', 'somevalue')
247 >>> u.setconfig(s, 'invalid', 'somevalue')
248 >>> u.configint(s, 'invalid')
248 >>> u.configint(s, 'invalid')
249 Traceback (most recent call last):
249 Traceback (most recent call last):
250 ...
250 ...
251 ConfigError: foo.invalid is not an integer ('somevalue')
251 ConfigError: foo.invalid is not an integer ('somevalue')
252 """
252 """
253
253
254 v = self.config(section, name, None, untrusted)
254 v = self.config(section, name, None, untrusted)
255 if v is None:
255 if v is None:
256 return default
256 return default
257 try:
257 try:
258 return int(v)
258 return int(v)
259 except ValueError:
259 except ValueError:
260 raise error.ConfigError(_("%s.%s is not an integer ('%s')")
260 raise error.ConfigError(_("%s.%s is not an integer ('%s')")
261 % (section, name, v))
261 % (section, name, v))
262
262
263 def configlist(self, section, name, default=None, untrusted=False):
263 def configlist(self, section, name, default=None, untrusted=False):
264 """parse a configuration element as a list of comma/space separated
264 """parse a configuration element as a list of comma/space separated
265 strings
265 strings
266
266
267 >>> u = ui(); s = 'foo'
267 >>> u = ui(); s = 'foo'
268 >>> u.setconfig(s, 'list1', 'this,is "a small" ,test')
268 >>> u.setconfig(s, 'list1', 'this,is "a small" ,test')
269 >>> u.configlist(s, 'list1')
269 >>> u.configlist(s, 'list1')
270 ['this', 'is', 'a small', 'test']
270 ['this', 'is', 'a small', 'test']
271 """
271 """
272
272
273 def _parse_plain(parts, s, offset):
273 def _parse_plain(parts, s, offset):
274 whitespace = False
274 whitespace = False
275 while offset < len(s) and (s[offset].isspace() or s[offset] == ','):
275 while offset < len(s) and (s[offset].isspace() or s[offset] == ','):
276 whitespace = True
276 whitespace = True
277 offset += 1
277 offset += 1
278 if offset >= len(s):
278 if offset >= len(s):
279 return None, parts, offset
279 return None, parts, offset
280 if whitespace:
280 if whitespace:
281 parts.append('')
281 parts.append('')
282 if s[offset] == '"' and not parts[-1]:
282 if s[offset] == '"' and not parts[-1]:
283 return _parse_quote, parts, offset + 1
283 return _parse_quote, parts, offset + 1
284 elif s[offset] == '"' and parts[-1][-1] == '\\':
284 elif s[offset] == '"' and parts[-1][-1] == '\\':
285 parts[-1] = parts[-1][:-1] + s[offset]
285 parts[-1] = parts[-1][:-1] + s[offset]
286 return _parse_plain, parts, offset + 1
286 return _parse_plain, parts, offset + 1
287 parts[-1] += s[offset]
287 parts[-1] += s[offset]
288 return _parse_plain, parts, offset + 1
288 return _parse_plain, parts, offset + 1
289
289
290 def _parse_quote(parts, s, offset):
290 def _parse_quote(parts, s, offset):
291 if offset < len(s) and s[offset] == '"': # ""
291 if offset < len(s) and s[offset] == '"': # ""
292 parts.append('')
292 parts.append('')
293 offset += 1
293 offset += 1
294 while offset < len(s) and (s[offset].isspace() or
294 while offset < len(s) and (s[offset].isspace() or
295 s[offset] == ','):
295 s[offset] == ','):
296 offset += 1
296 offset += 1
297 return _parse_plain, parts, offset
297 return _parse_plain, parts, offset
298
298
299 while offset < len(s) and s[offset] != '"':
299 while offset < len(s) and s[offset] != '"':
300 if (s[offset] == '\\' and offset + 1 < len(s)
300 if (s[offset] == '\\' and offset + 1 < len(s)
301 and s[offset + 1] == '"'):
301 and s[offset + 1] == '"'):
302 offset += 1
302 offset += 1
303 parts[-1] += '"'
303 parts[-1] += '"'
304 else:
304 else:
305 parts[-1] += s[offset]
305 parts[-1] += s[offset]
306 offset += 1
306 offset += 1
307
307
308 if offset >= len(s):
308 if offset >= len(s):
309 real_parts = _configlist(parts[-1])
309 real_parts = _configlist(parts[-1])
310 if not real_parts:
310 if not real_parts:
311 parts[-1] = '"'
311 parts[-1] = '"'
312 else:
312 else:
313 real_parts[0] = '"' + real_parts[0]
313 real_parts[0] = '"' + real_parts[0]
314 parts = parts[:-1]
314 parts = parts[:-1]
315 parts.extend(real_parts)
315 parts.extend(real_parts)
316 return None, parts, offset
316 return None, parts, offset
317
317
318 offset += 1
318 offset += 1
319 while offset < len(s) and s[offset] in [' ', ',']:
319 while offset < len(s) and s[offset] in [' ', ',']:
320 offset += 1
320 offset += 1
321
321
322 if offset < len(s):
322 if offset < len(s):
323 if offset + 1 == len(s) and s[offset] == '"':
323 if offset + 1 == len(s) and s[offset] == '"':
324 parts[-1] += '"'
324 parts[-1] += '"'
325 offset += 1
325 offset += 1
326 else:
326 else:
327 parts.append('')
327 parts.append('')
328 else:
328 else:
329 return None, parts, offset
329 return None, parts, offset
330
330
331 return _parse_plain, parts, offset
331 return _parse_plain, parts, offset
332
332
333 def _configlist(s):
333 def _configlist(s):
334 s = s.rstrip(' ,')
334 s = s.rstrip(' ,')
335 if not s:
335 if not s:
336 return []
336 return []
337 parser, parts, offset = _parse_plain, [''], 0
337 parser, parts, offset = _parse_plain, [''], 0
338 while parser:
338 while parser:
339 parser, parts, offset = parser(parts, s, offset)
339 parser, parts, offset = parser(parts, s, offset)
340 return parts
340 return parts
341
341
342 result = self.config(section, name, untrusted=untrusted)
342 result = self.config(section, name, untrusted=untrusted)
343 if result is None:
343 if result is None:
344 result = default or []
344 result = default or []
345 if isinstance(result, basestring):
345 if isinstance(result, basestring):
346 result = _configlist(result.lstrip(' ,\n'))
346 result = _configlist(result.lstrip(' ,\n'))
347 if result is None:
347 if result is None:
348 result = default or []
348 result = default or []
349 return result
349 return result
350
350
351 def has_section(self, section, untrusted=False):
351 def has_section(self, section, untrusted=False):
352 '''tell whether section exists in config.'''
352 '''tell whether section exists in config.'''
353 return section in self._data(untrusted)
353 return section in self._data(untrusted)
354
354
355 def configitems(self, section, untrusted=False):
355 def configitems(self, section, untrusted=False):
356 items = self._data(untrusted).items(section)
356 items = self._data(untrusted).items(section)
357 if self.debugflag and not untrusted and self._reportuntrusted:
357 if self.debugflag and not untrusted and self._reportuntrusted:
358 for k, v in self._ucfg.items(section):
358 for k, v in self._ucfg.items(section):
359 if self._tcfg.get(section, k) != v:
359 if self._tcfg.get(section, k) != v:
360 self.debug("ignoring untrusted configuration option "
360 self.debug("ignoring untrusted configuration option "
361 "%s.%s = %s\n" % (section, k, v))
361 "%s.%s = %s\n" % (section, k, v))
362 return items
362 return items
363
363
364 def walkconfig(self, untrusted=False):
364 def walkconfig(self, untrusted=False):
365 cfg = self._data(untrusted)
365 cfg = self._data(untrusted)
366 for section in cfg.sections():
366 for section in cfg.sections():
367 for name, value in self.configitems(section, untrusted):
367 for name, value in self.configitems(section, untrusted):
368 yield section, name, value
368 yield section, name, value
369
369
370 def plain(self, feature=None):
370 def plain(self, feature=None):
371 '''is plain mode active?
371 '''is plain mode active?
372
372
373 Plain mode means that all configuration variables which affect
373 Plain mode means that all configuration variables which affect
374 the behavior and output of Mercurial should be
374 the behavior and output of Mercurial should be
375 ignored. Additionally, the output should be stable,
375 ignored. Additionally, the output should be stable,
376 reproducible and suitable for use in scripts or applications.
376 reproducible and suitable for use in scripts or applications.
377
377
378 The only way to trigger plain mode is by setting either the
378 The only way to trigger plain mode is by setting either the
379 `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
379 `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
380
380
381 The return value can either be
381 The return value can either be
382 - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
382 - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
383 - True otherwise
383 - True otherwise
384 '''
384 '''
385 if 'HGPLAIN' not in os.environ and 'HGPLAINEXCEPT' not in os.environ:
385 if 'HGPLAIN' not in os.environ and 'HGPLAINEXCEPT' not in os.environ:
386 return False
386 return False
387 exceptions = os.environ.get('HGPLAINEXCEPT', '').strip().split(',')
387 exceptions = os.environ.get('HGPLAINEXCEPT', '').strip().split(',')
388 if feature and exceptions:
388 if feature and exceptions:
389 return feature not in exceptions
389 return feature not in exceptions
390 return True
390 return True
391
391
392 def username(self):
392 def username(self):
393 """Return default username to be used in commits.
393 """Return default username to be used in commits.
394
394
395 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
395 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
396 and stop searching if one of these is set.
396 and stop searching if one of these is set.
397 If not found and ui.askusername is True, ask the user, else use
397 If not found and ui.askusername is True, ask the user, else use
398 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
398 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
399 """
399 """
400 user = os.environ.get("HGUSER")
400 user = os.environ.get("HGUSER")
401 if user is None:
401 if user is None:
402 user = self.config("ui", "username")
402 user = self.config("ui", "username")
403 if user is not None:
403 if user is not None:
404 user = os.path.expandvars(user)
404 user = os.path.expandvars(user)
405 if user is None:
405 if user is None:
406 user = os.environ.get("EMAIL")
406 user = os.environ.get("EMAIL")
407 if user is None and self.configbool("ui", "askusername"):
407 if user is None and self.configbool("ui", "askusername"):
408 user = self.prompt(_("enter a commit username:"), default=None)
408 user = self.prompt(_("enter a commit username:"), default=None)
409 if user is None and not self.interactive():
409 if user is None and not self.interactive():
410 try:
410 try:
411 user = '%s@%s' % (util.getuser(), socket.getfqdn())
411 user = '%s@%s' % (util.getuser(), socket.getfqdn())
412 self.warn(_("No username found, using '%s' instead\n") % user)
412 self.warn(_("No username found, using '%s' instead\n") % user)
413 except KeyError:
413 except KeyError:
414 pass
414 pass
415 if not user:
415 if not user:
416 raise util.Abort(_('no username supplied (see "hg help config")'))
416 raise util.Abort(_('no username supplied (see "hg help config")'))
417 if "\n" in user:
417 if "\n" in user:
418 raise util.Abort(_("username %s contains a newline\n") % repr(user))
418 raise util.Abort(_("username %s contains a newline\n") % repr(user))
419 return user
419 return user
420
420
421 def shortuser(self, user):
421 def shortuser(self, user):
422 """Return a short representation of a user name or email address."""
422 """Return a short representation of a user name or email address."""
423 if not self.verbose:
423 if not self.verbose:
424 user = util.shortuser(user)
424 user = util.shortuser(user)
425 return user
425 return user
426
426
427 def expandpath(self, loc, default=None):
427 def expandpath(self, loc, default=None):
428 """Return repository location relative to cwd or from [paths]"""
428 """Return repository location relative to cwd or from [paths]"""
429 if util.hasscheme(loc) or os.path.isdir(os.path.join(loc, '.hg')):
429 if util.hasscheme(loc) or os.path.isdir(os.path.join(loc, '.hg')):
430 return loc
430 return loc
431
431
432 path = self.config('paths', loc)
432 path = self.config('paths', loc)
433 if not path and default is not None:
433 if not path and default is not None:
434 path = self.config('paths', default)
434 path = self.config('paths', default)
435 return path or loc
435 return path or loc
436
436
437 def pushbuffer(self):
437 def pushbuffer(self):
438 self._buffers.append([])
438 self._buffers.append([])
439
439
440 def popbuffer(self, labeled=False):
440 def popbuffer(self, labeled=False):
441 '''pop the last buffer and return the buffered output
441 '''pop the last buffer and return the buffered output
442
442
443 If labeled is True, any labels associated with buffered
443 If labeled is True, any labels associated with buffered
444 output will be handled. By default, this has no effect
444 output will be handled. By default, this has no effect
445 on the output returned, but extensions and GUI tools may
445 on the output returned, but extensions and GUI tools may
446 handle this argument and returned styled output. If output
446 handle this argument and returned styled output. If output
447 is being buffered so it can be captured and parsed or
447 is being buffered so it can be captured and parsed or
448 processed, labeled should not be set to True.
448 processed, labeled should not be set to True.
449 '''
449 '''
450 return "".join(self._buffers.pop())
450 return "".join(self._buffers.pop())
451
451
452 def write(self, *args, **opts):
452 def write(self, *args, **opts):
453 '''write args to output
453 '''write args to output
454
454
455 By default, this method simply writes to the buffer or stdout,
455 By default, this method simply writes to the buffer or stdout,
456 but extensions or GUI tools may override this method,
456 but extensions or GUI tools may override this method,
457 write_err(), popbuffer(), and label() to style output from
457 write_err(), popbuffer(), and label() to style output from
458 various parts of hg.
458 various parts of hg.
459
459
460 An optional keyword argument, "label", can be passed in.
460 An optional keyword argument, "label", can be passed in.
461 This should be a string containing label names separated by
461 This should be a string containing label names separated by
462 space. Label names take the form of "topic.type". For example,
462 space. Label names take the form of "topic.type". For example,
463 ui.debug() issues a label of "ui.debug".
463 ui.debug() issues a label of "ui.debug".
464
464
465 When labeling output for a specific command, a label of
465 When labeling output for a specific command, a label of
466 "cmdname.type" is recommended. For example, status issues
466 "cmdname.type" is recommended. For example, status issues
467 a label of "status.modified" for modified files.
467 a label of "status.modified" for modified files.
468 '''
468 '''
469 if self._buffers:
469 if self._buffers:
470 self._buffers[-1].extend([str(a) for a in args])
470 self._buffers[-1].extend([str(a) for a in args])
471 else:
471 else:
472 for a in args:
472 for a in args:
473 self.fout.write(str(a))
473 self.fout.write(str(a))
474
474
475 def write_err(self, *args, **opts):
475 def write_err(self, *args, **opts):
476 try:
476 try:
477 if not getattr(self.fout, 'closed', False):
477 if not getattr(self.fout, 'closed', False):
478 self.fout.flush()
478 self.fout.flush()
479 for a in args:
479 for a in args:
480 self.ferr.write(str(a))
480 self.ferr.write(str(a))
481 # stderr may be buffered under win32 when redirected to files,
481 # stderr may be buffered under win32 when redirected to files,
482 # including stdout.
482 # including stdout.
483 if not getattr(self.ferr, 'closed', False):
483 if not getattr(self.ferr, 'closed', False):
484 self.ferr.flush()
484 self.ferr.flush()
485 except IOError, inst:
485 except IOError, inst:
486 if inst.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
486 if inst.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
487 raise
487 raise
488
488
489 def flush(self):
489 def flush(self):
490 try: self.fout.flush()
490 try: self.fout.flush()
491 except: pass
491 except (IOError, ValueError): pass
492 try: self.ferr.flush()
492 try: self.ferr.flush()
493 except: pass
493 except (IOError, ValueError): pass
494
494
495 def interactive(self):
495 def interactive(self):
496 '''is interactive input allowed?
496 '''is interactive input allowed?
497
497
498 An interactive session is a session where input can be reasonably read
498 An interactive session is a session where input can be reasonably read
499 from `sys.stdin'. If this function returns false, any attempt to read
499 from `sys.stdin'. If this function returns false, any attempt to read
500 from stdin should fail with an error, unless a sensible default has been
500 from stdin should fail with an error, unless a sensible default has been
501 specified.
501 specified.
502
502
503 Interactiveness is triggered by the value of the `ui.interactive'
503 Interactiveness is triggered by the value of the `ui.interactive'
504 configuration variable or - if it is unset - when `sys.stdin' points
504 configuration variable or - if it is unset - when `sys.stdin' points
505 to a terminal device.
505 to a terminal device.
506
506
507 This function refers to input only; for output, see `ui.formatted()'.
507 This function refers to input only; for output, see `ui.formatted()'.
508 '''
508 '''
509 i = self.configbool("ui", "interactive", None)
509 i = self.configbool("ui", "interactive", None)
510 if i is None:
510 if i is None:
511 # some environments replace stdin without implementing isatty
511 # some environments replace stdin without implementing isatty
512 # usually those are non-interactive
512 # usually those are non-interactive
513 return util.isatty(self.fin)
513 return util.isatty(self.fin)
514
514
515 return i
515 return i
516
516
517 def termwidth(self):
517 def termwidth(self):
518 '''how wide is the terminal in columns?
518 '''how wide is the terminal in columns?
519 '''
519 '''
520 if 'COLUMNS' in os.environ:
520 if 'COLUMNS' in os.environ:
521 try:
521 try:
522 return int(os.environ['COLUMNS'])
522 return int(os.environ['COLUMNS'])
523 except ValueError:
523 except ValueError:
524 pass
524 pass
525 return util.termwidth()
525 return util.termwidth()
526
526
527 def formatted(self):
527 def formatted(self):
528 '''should formatted output be used?
528 '''should formatted output be used?
529
529
530 It is often desirable to format the output to suite the output medium.
530 It is often desirable to format the output to suite the output medium.
531 Examples of this are truncating long lines or colorizing messages.
531 Examples of this are truncating long lines or colorizing messages.
532 However, this is not often not desirable when piping output into other
532 However, this is not often not desirable when piping output into other
533 utilities, e.g. `grep'.
533 utilities, e.g. `grep'.
534
534
535 Formatted output is triggered by the value of the `ui.formatted'
535 Formatted output is triggered by the value of the `ui.formatted'
536 configuration variable or - if it is unset - when `sys.stdout' points
536 configuration variable or - if it is unset - when `sys.stdout' points
537 to a terminal device. Please note that `ui.formatted' should be
537 to a terminal device. Please note that `ui.formatted' should be
538 considered an implementation detail; it is not intended for use outside
538 considered an implementation detail; it is not intended for use outside
539 Mercurial or its extensions.
539 Mercurial or its extensions.
540
540
541 This function refers to output only; for input, see `ui.interactive()'.
541 This function refers to output only; for input, see `ui.interactive()'.
542 This function always returns false when in plain mode, see `ui.plain()'.
542 This function always returns false when in plain mode, see `ui.plain()'.
543 '''
543 '''
544 if self.plain():
544 if self.plain():
545 return False
545 return False
546
546
547 i = self.configbool("ui", "formatted", None)
547 i = self.configbool("ui", "formatted", None)
548 if i is None:
548 if i is None:
549 # some environments replace stdout without implementing isatty
549 # some environments replace stdout without implementing isatty
550 # usually those are non-interactive
550 # usually those are non-interactive
551 return util.isatty(self.fout)
551 return util.isatty(self.fout)
552
552
553 return i
553 return i
554
554
555 def _readline(self, prompt=''):
555 def _readline(self, prompt=''):
556 if util.isatty(self.fin):
556 if util.isatty(self.fin):
557 try:
557 try:
558 # magically add command line editing support, where
558 # magically add command line editing support, where
559 # available
559 # available
560 import readline
560 import readline
561 # force demandimport to really load the module
561 # force demandimport to really load the module
562 readline.read_history_file
562 readline.read_history_file
563 # windows sometimes raises something other than ImportError
563 # windows sometimes raises something other than ImportError
564 except Exception:
564 except Exception:
565 pass
565 pass
566
566
567 # call write() so output goes through subclassed implementation
567 # call write() so output goes through subclassed implementation
568 # e.g. color extension on Windows
568 # e.g. color extension on Windows
569 self.write(prompt)
569 self.write(prompt)
570
570
571 # instead of trying to emulate raw_input, swap (self.fin,
571 # instead of trying to emulate raw_input, swap (self.fin,
572 # self.fout) with (sys.stdin, sys.stdout)
572 # self.fout) with (sys.stdin, sys.stdout)
573 oldin = sys.stdin
573 oldin = sys.stdin
574 oldout = sys.stdout
574 oldout = sys.stdout
575 sys.stdin = self.fin
575 sys.stdin = self.fin
576 sys.stdout = self.fout
576 sys.stdout = self.fout
577 line = raw_input(' ')
577 line = raw_input(' ')
578 sys.stdin = oldin
578 sys.stdin = oldin
579 sys.stdout = oldout
579 sys.stdout = oldout
580
580
581 # When stdin is in binary mode on Windows, it can cause
581 # When stdin is in binary mode on Windows, it can cause
582 # raw_input() to emit an extra trailing carriage return
582 # raw_input() to emit an extra trailing carriage return
583 if os.linesep == '\r\n' and line and line[-1] == '\r':
583 if os.linesep == '\r\n' and line and line[-1] == '\r':
584 line = line[:-1]
584 line = line[:-1]
585 return line
585 return line
586
586
587 def prompt(self, msg, default="y"):
587 def prompt(self, msg, default="y"):
588 """Prompt user with msg, read response.
588 """Prompt user with msg, read response.
589 If ui is not interactive, the default is returned.
589 If ui is not interactive, the default is returned.
590 """
590 """
591 if not self.interactive():
591 if not self.interactive():
592 self.write(msg, ' ', default, "\n")
592 self.write(msg, ' ', default, "\n")
593 return default
593 return default
594 try:
594 try:
595 r = self._readline(self.label(msg, 'ui.prompt'))
595 r = self._readline(self.label(msg, 'ui.prompt'))
596 if not r:
596 if not r:
597 return default
597 return default
598 return r
598 return r
599 except EOFError:
599 except EOFError:
600 raise util.Abort(_('response expected'))
600 raise util.Abort(_('response expected'))
601
601
602 def promptchoice(self, msg, choices, default=0):
602 def promptchoice(self, msg, choices, default=0):
603 """Prompt user with msg, read response, and ensure it matches
603 """Prompt user with msg, read response, and ensure it matches
604 one of the provided choices. The index of the choice is returned.
604 one of the provided choices. The index of the choice is returned.
605 choices is a sequence of acceptable responses with the format:
605 choices is a sequence of acceptable responses with the format:
606 ('&None', 'E&xec', 'Sym&link') Responses are case insensitive.
606 ('&None', 'E&xec', 'Sym&link') Responses are case insensitive.
607 If ui is not interactive, the default is returned.
607 If ui is not interactive, the default is returned.
608 """
608 """
609 resps = [s[s.index('&')+1].lower() for s in choices]
609 resps = [s[s.index('&')+1].lower() for s in choices]
610 while True:
610 while True:
611 r = self.prompt(msg, resps[default])
611 r = self.prompt(msg, resps[default])
612 if r.lower() in resps:
612 if r.lower() in resps:
613 return resps.index(r.lower())
613 return resps.index(r.lower())
614 self.write(_("unrecognized response\n"))
614 self.write(_("unrecognized response\n"))
615
615
616 def getpass(self, prompt=None, default=None):
616 def getpass(self, prompt=None, default=None):
617 if not self.interactive():
617 if not self.interactive():
618 return default
618 return default
619 try:
619 try:
620 return getpass.getpass(prompt or _('password: '))
620 return getpass.getpass(prompt or _('password: '))
621 except EOFError:
621 except EOFError:
622 raise util.Abort(_('response expected'))
622 raise util.Abort(_('response expected'))
623 def status(self, *msg, **opts):
623 def status(self, *msg, **opts):
624 '''write status message to output (if ui.quiet is False)
624 '''write status message to output (if ui.quiet is False)
625
625
626 This adds an output label of "ui.status".
626 This adds an output label of "ui.status".
627 '''
627 '''
628 if not self.quiet:
628 if not self.quiet:
629 opts['label'] = opts.get('label', '') + ' ui.status'
629 opts['label'] = opts.get('label', '') + ' ui.status'
630 self.write(*msg, **opts)
630 self.write(*msg, **opts)
631 def warn(self, *msg, **opts):
631 def warn(self, *msg, **opts):
632 '''write warning message to output (stderr)
632 '''write warning message to output (stderr)
633
633
634 This adds an output label of "ui.warning".
634 This adds an output label of "ui.warning".
635 '''
635 '''
636 opts['label'] = opts.get('label', '') + ' ui.warning'
636 opts['label'] = opts.get('label', '') + ' ui.warning'
637 self.write_err(*msg, **opts)
637 self.write_err(*msg, **opts)
638 def note(self, *msg, **opts):
638 def note(self, *msg, **opts):
639 '''write note to output (if ui.verbose is True)
639 '''write note to output (if ui.verbose is True)
640
640
641 This adds an output label of "ui.note".
641 This adds an output label of "ui.note".
642 '''
642 '''
643 if self.verbose:
643 if self.verbose:
644 opts['label'] = opts.get('label', '') + ' ui.note'
644 opts['label'] = opts.get('label', '') + ' ui.note'
645 self.write(*msg, **opts)
645 self.write(*msg, **opts)
646 def debug(self, *msg, **opts):
646 def debug(self, *msg, **opts):
647 '''write debug message to output (if ui.debugflag is True)
647 '''write debug message to output (if ui.debugflag is True)
648
648
649 This adds an output label of "ui.debug".
649 This adds an output label of "ui.debug".
650 '''
650 '''
651 if self.debugflag:
651 if self.debugflag:
652 opts['label'] = opts.get('label', '') + ' ui.debug'
652 opts['label'] = opts.get('label', '') + ' ui.debug'
653 self.write(*msg, **opts)
653 self.write(*msg, **opts)
654 def edit(self, text, user):
654 def edit(self, text, user):
655 (fd, name) = tempfile.mkstemp(prefix="hg-editor-", suffix=".txt",
655 (fd, name) = tempfile.mkstemp(prefix="hg-editor-", suffix=".txt",
656 text=True)
656 text=True)
657 try:
657 try:
658 f = os.fdopen(fd, "w")
658 f = os.fdopen(fd, "w")
659 f.write(text)
659 f.write(text)
660 f.close()
660 f.close()
661
661
662 editor = self.geteditor()
662 editor = self.geteditor()
663
663
664 util.system("%s \"%s\"" % (editor, name),
664 util.system("%s \"%s\"" % (editor, name),
665 environ={'HGUSER': user},
665 environ={'HGUSER': user},
666 onerr=util.Abort, errprefix=_("edit failed"),
666 onerr=util.Abort, errprefix=_("edit failed"),
667 out=self.fout)
667 out=self.fout)
668
668
669 f = open(name)
669 f = open(name)
670 t = f.read()
670 t = f.read()
671 f.close()
671 f.close()
672 finally:
672 finally:
673 os.unlink(name)
673 os.unlink(name)
674
674
675 return t
675 return t
676
676
677 def traceback(self, exc=None):
677 def traceback(self, exc=None):
678 '''print exception traceback if traceback printing enabled.
678 '''print exception traceback if traceback printing enabled.
679 only to call in exception handler. returns true if traceback
679 only to call in exception handler. returns true if traceback
680 printed.'''
680 printed.'''
681 if self.tracebackflag:
681 if self.tracebackflag:
682 if exc:
682 if exc:
683 traceback.print_exception(exc[0], exc[1], exc[2],
683 traceback.print_exception(exc[0], exc[1], exc[2],
684 file=self.ferr)
684 file=self.ferr)
685 else:
685 else:
686 traceback.print_exc(file=self.ferr)
686 traceback.print_exc(file=self.ferr)
687 return self.tracebackflag
687 return self.tracebackflag
688
688
689 def geteditor(self):
689 def geteditor(self):
690 '''return editor to use'''
690 '''return editor to use'''
691 if sys.platform == 'plan9':
691 if sys.platform == 'plan9':
692 # vi is the MIPS instruction simulator on Plan 9. We
692 # vi is the MIPS instruction simulator on Plan 9. We
693 # instead default to E to plumb commit messages to
693 # instead default to E to plumb commit messages to
694 # avoid confusion.
694 # avoid confusion.
695 editor = 'E'
695 editor = 'E'
696 else:
696 else:
697 editor = 'vi'
697 editor = 'vi'
698 return (os.environ.get("HGEDITOR") or
698 return (os.environ.get("HGEDITOR") or
699 self.config("ui", "editor") or
699 self.config("ui", "editor") or
700 os.environ.get("VISUAL") or
700 os.environ.get("VISUAL") or
701 os.environ.get("EDITOR", editor))
701 os.environ.get("EDITOR", editor))
702
702
703 def progress(self, topic, pos, item="", unit="", total=None):
703 def progress(self, topic, pos, item="", unit="", total=None):
704 '''show a progress message
704 '''show a progress message
705
705
706 With stock hg, this is simply a debug message that is hidden
706 With stock hg, this is simply a debug message that is hidden
707 by default, but with extensions or GUI tools it may be
707 by default, but with extensions or GUI tools it may be
708 visible. 'topic' is the current operation, 'item' is a
708 visible. 'topic' is the current operation, 'item' is a
709 non-numeric marker of the current position (ie the currently
709 non-numeric marker of the current position (ie the currently
710 in-process file), 'pos' is the current numeric position (ie
710 in-process file), 'pos' is the current numeric position (ie
711 revision, bytes, etc.), unit is a corresponding unit label,
711 revision, bytes, etc.), unit is a corresponding unit label,
712 and total is the highest expected pos.
712 and total is the highest expected pos.
713
713
714 Multiple nested topics may be active at a time.
714 Multiple nested topics may be active at a time.
715
715
716 All topics should be marked closed by setting pos to None at
716 All topics should be marked closed by setting pos to None at
717 termination.
717 termination.
718 '''
718 '''
719
719
720 if pos is None or not self.debugflag:
720 if pos is None or not self.debugflag:
721 return
721 return
722
722
723 if unit:
723 if unit:
724 unit = ' ' + unit
724 unit = ' ' + unit
725 if item:
725 if item:
726 item = ' ' + item
726 item = ' ' + item
727
727
728 if total:
728 if total:
729 pct = 100.0 * pos / total
729 pct = 100.0 * pos / total
730 self.debug('%s:%s %s/%s%s (%4.2f%%)\n'
730 self.debug('%s:%s %s/%s%s (%4.2f%%)\n'
731 % (topic, item, pos, total, unit, pct))
731 % (topic, item, pos, total, unit, pct))
732 else:
732 else:
733 self.debug('%s:%s %s%s\n' % (topic, item, pos, unit))
733 self.debug('%s:%s %s%s\n' % (topic, item, pos, unit))
734
734
735 def log(self, service, message):
735 def log(self, service, message):
736 '''hook for logging facility extensions
736 '''hook for logging facility extensions
737
737
738 service should be a readily-identifiable subsystem, which will
738 service should be a readily-identifiable subsystem, which will
739 allow filtering.
739 allow filtering.
740 message should be a newline-terminated string to log.
740 message should be a newline-terminated string to log.
741 '''
741 '''
742 pass
742 pass
743
743
744 def label(self, msg, label):
744 def label(self, msg, label):
745 '''style msg based on supplied label
745 '''style msg based on supplied label
746
746
747 Like ui.write(), this just returns msg unchanged, but extensions
747 Like ui.write(), this just returns msg unchanged, but extensions
748 and GUI tools can override it to allow styling output without
748 and GUI tools can override it to allow styling output without
749 writing it.
749 writing it.
750
750
751 ui.write(s, 'label') is equivalent to
751 ui.write(s, 'label') is equivalent to
752 ui.write(ui.label(s, 'label')).
752 ui.write(ui.label(s, 'label')).
753 '''
753 '''
754 return msg
754 return msg
@@ -1,1766 +1,1766
1 # util.py - Mercurial utility functions and platform specfic implementations
1 # util.py - Mercurial utility functions and platform specfic implementations
2 #
2 #
3 # Copyright 2005 K. Thananchayan <thananck@yahoo.com>
3 # Copyright 2005 K. Thananchayan <thananck@yahoo.com>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
6 #
6 #
7 # This software may be used and distributed according to the terms of the
7 # This software may be used and distributed according to the terms of the
8 # GNU General Public License version 2 or any later version.
8 # GNU General Public License version 2 or any later version.
9
9
10 """Mercurial utility functions and platform specfic implementations.
10 """Mercurial utility functions and platform specfic implementations.
11
11
12 This contains helper routines that are independent of the SCM core and
12 This contains helper routines that are independent of the SCM core and
13 hide platform-specific details from the core.
13 hide platform-specific details from the core.
14 """
14 """
15
15
16 from i18n import _
16 from i18n import _
17 import error, osutil, encoding
17 import error, osutil, encoding
18 import errno, re, shutil, sys, tempfile, traceback
18 import errno, re, shutil, sys, tempfile, traceback
19 import os, time, datetime, calendar, textwrap, signal
19 import os, time, datetime, calendar, textwrap, signal
20 import imp, socket, urllib
20 import imp, socket, urllib
21
21
22 if os.name == 'nt':
22 if os.name == 'nt':
23 import windows as platform
23 import windows as platform
24 else:
24 else:
25 import posix as platform
25 import posix as platform
26
26
27 platform.encodinglower = encoding.lower
27 platform.encodinglower = encoding.lower
28 platform.encodingupper = encoding.upper
28 platform.encodingupper = encoding.upper
29
29
30 cachestat = platform.cachestat
30 cachestat = platform.cachestat
31 checkexec = platform.checkexec
31 checkexec = platform.checkexec
32 checklink = platform.checklink
32 checklink = platform.checklink
33 copymode = platform.copymode
33 copymode = platform.copymode
34 executablepath = platform.executablepath
34 executablepath = platform.executablepath
35 expandglobs = platform.expandglobs
35 expandglobs = platform.expandglobs
36 explainexit = platform.explainexit
36 explainexit = platform.explainexit
37 findexe = platform.findexe
37 findexe = platform.findexe
38 gethgcmd = platform.gethgcmd
38 gethgcmd = platform.gethgcmd
39 getuser = platform.getuser
39 getuser = platform.getuser
40 groupmembers = platform.groupmembers
40 groupmembers = platform.groupmembers
41 groupname = platform.groupname
41 groupname = platform.groupname
42 hidewindow = platform.hidewindow
42 hidewindow = platform.hidewindow
43 isexec = platform.isexec
43 isexec = platform.isexec
44 isowner = platform.isowner
44 isowner = platform.isowner
45 localpath = platform.localpath
45 localpath = platform.localpath
46 lookupreg = platform.lookupreg
46 lookupreg = platform.lookupreg
47 makedir = platform.makedir
47 makedir = platform.makedir
48 nlinks = platform.nlinks
48 nlinks = platform.nlinks
49 normpath = platform.normpath
49 normpath = platform.normpath
50 normcase = platform.normcase
50 normcase = platform.normcase
51 nulldev = platform.nulldev
51 nulldev = platform.nulldev
52 openhardlinks = platform.openhardlinks
52 openhardlinks = platform.openhardlinks
53 oslink = platform.oslink
53 oslink = platform.oslink
54 parsepatchoutput = platform.parsepatchoutput
54 parsepatchoutput = platform.parsepatchoutput
55 pconvert = platform.pconvert
55 pconvert = platform.pconvert
56 popen = platform.popen
56 popen = platform.popen
57 posixfile = platform.posixfile
57 posixfile = platform.posixfile
58 quotecommand = platform.quotecommand
58 quotecommand = platform.quotecommand
59 realpath = platform.realpath
59 realpath = platform.realpath
60 rename = platform.rename
60 rename = platform.rename
61 samedevice = platform.samedevice
61 samedevice = platform.samedevice
62 samefile = platform.samefile
62 samefile = platform.samefile
63 samestat = platform.samestat
63 samestat = platform.samestat
64 setbinary = platform.setbinary
64 setbinary = platform.setbinary
65 setflags = platform.setflags
65 setflags = platform.setflags
66 setsignalhandler = platform.setsignalhandler
66 setsignalhandler = platform.setsignalhandler
67 shellquote = platform.shellquote
67 shellquote = platform.shellquote
68 spawndetached = platform.spawndetached
68 spawndetached = platform.spawndetached
69 sshargs = platform.sshargs
69 sshargs = platform.sshargs
70 statfiles = platform.statfiles
70 statfiles = platform.statfiles
71 termwidth = platform.termwidth
71 termwidth = platform.termwidth
72 testpid = platform.testpid
72 testpid = platform.testpid
73 umask = platform.umask
73 umask = platform.umask
74 unlink = platform.unlink
74 unlink = platform.unlink
75 unlinkpath = platform.unlinkpath
75 unlinkpath = platform.unlinkpath
76 username = platform.username
76 username = platform.username
77
77
78 # Python compatibility
78 # Python compatibility
79
79
80 _notset = object()
80 _notset = object()
81
81
82 def safehasattr(thing, attr):
82 def safehasattr(thing, attr):
83 return getattr(thing, attr, _notset) is not _notset
83 return getattr(thing, attr, _notset) is not _notset
84
84
85 def sha1(s=''):
85 def sha1(s=''):
86 '''
86 '''
87 Low-overhead wrapper around Python's SHA support
87 Low-overhead wrapper around Python's SHA support
88
88
89 >>> f = _fastsha1
89 >>> f = _fastsha1
90 >>> a = sha1()
90 >>> a = sha1()
91 >>> a = f()
91 >>> a = f()
92 >>> a.hexdigest()
92 >>> a.hexdigest()
93 'da39a3ee5e6b4b0d3255bfef95601890afd80709'
93 'da39a3ee5e6b4b0d3255bfef95601890afd80709'
94 '''
94 '''
95
95
96 return _fastsha1(s)
96 return _fastsha1(s)
97
97
98 def _fastsha1(s=''):
98 def _fastsha1(s=''):
99 # This function will import sha1 from hashlib or sha (whichever is
99 # This function will import sha1 from hashlib or sha (whichever is
100 # available) and overwrite itself with it on the first call.
100 # available) and overwrite itself with it on the first call.
101 # Subsequent calls will go directly to the imported function.
101 # Subsequent calls will go directly to the imported function.
102 if sys.version_info >= (2, 5):
102 if sys.version_info >= (2, 5):
103 from hashlib import sha1 as _sha1
103 from hashlib import sha1 as _sha1
104 else:
104 else:
105 from sha import sha as _sha1
105 from sha import sha as _sha1
106 global _fastsha1, sha1
106 global _fastsha1, sha1
107 _fastsha1 = sha1 = _sha1
107 _fastsha1 = sha1 = _sha1
108 return _sha1(s)
108 return _sha1(s)
109
109
110 try:
110 try:
111 buffer = buffer
111 buffer = buffer
112 except NameError:
112 except NameError:
113 if sys.version_info[0] < 3:
113 if sys.version_info[0] < 3:
114 def buffer(sliceable, offset=0):
114 def buffer(sliceable, offset=0):
115 return sliceable[offset:]
115 return sliceable[offset:]
116 else:
116 else:
117 def buffer(sliceable, offset=0):
117 def buffer(sliceable, offset=0):
118 return memoryview(sliceable)[offset:]
118 return memoryview(sliceable)[offset:]
119
119
120 import subprocess
120 import subprocess
121 closefds = os.name == 'posix'
121 closefds = os.name == 'posix'
122
122
123 def popen2(cmd, env=None, newlines=False):
123 def popen2(cmd, env=None, newlines=False):
124 # Setting bufsize to -1 lets the system decide the buffer size.
124 # Setting bufsize to -1 lets the system decide the buffer size.
125 # The default for bufsize is 0, meaning unbuffered. This leads to
125 # The default for bufsize is 0, meaning unbuffered. This leads to
126 # poor performance on Mac OS X: http://bugs.python.org/issue4194
126 # poor performance on Mac OS X: http://bugs.python.org/issue4194
127 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
127 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
128 close_fds=closefds,
128 close_fds=closefds,
129 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
129 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
130 universal_newlines=newlines,
130 universal_newlines=newlines,
131 env=env)
131 env=env)
132 return p.stdin, p.stdout
132 return p.stdin, p.stdout
133
133
134 def popen3(cmd, env=None, newlines=False):
134 def popen3(cmd, env=None, newlines=False):
135 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
135 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
136 close_fds=closefds,
136 close_fds=closefds,
137 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
137 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
138 stderr=subprocess.PIPE,
138 stderr=subprocess.PIPE,
139 universal_newlines=newlines,
139 universal_newlines=newlines,
140 env=env)
140 env=env)
141 return p.stdin, p.stdout, p.stderr
141 return p.stdin, p.stdout, p.stderr
142
142
143 def version():
143 def version():
144 """Return version information if available."""
144 """Return version information if available."""
145 try:
145 try:
146 import __version__
146 import __version__
147 return __version__.version
147 return __version__.version
148 except ImportError:
148 except ImportError:
149 return 'unknown'
149 return 'unknown'
150
150
151 # used by parsedate
151 # used by parsedate
152 defaultdateformats = (
152 defaultdateformats = (
153 '%Y-%m-%d %H:%M:%S',
153 '%Y-%m-%d %H:%M:%S',
154 '%Y-%m-%d %I:%M:%S%p',
154 '%Y-%m-%d %I:%M:%S%p',
155 '%Y-%m-%d %H:%M',
155 '%Y-%m-%d %H:%M',
156 '%Y-%m-%d %I:%M%p',
156 '%Y-%m-%d %I:%M%p',
157 '%Y-%m-%d',
157 '%Y-%m-%d',
158 '%m-%d',
158 '%m-%d',
159 '%m/%d',
159 '%m/%d',
160 '%m/%d/%y',
160 '%m/%d/%y',
161 '%m/%d/%Y',
161 '%m/%d/%Y',
162 '%a %b %d %H:%M:%S %Y',
162 '%a %b %d %H:%M:%S %Y',
163 '%a %b %d %I:%M:%S%p %Y',
163 '%a %b %d %I:%M:%S%p %Y',
164 '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
164 '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
165 '%b %d %H:%M:%S %Y',
165 '%b %d %H:%M:%S %Y',
166 '%b %d %I:%M:%S%p %Y',
166 '%b %d %I:%M:%S%p %Y',
167 '%b %d %H:%M:%S',
167 '%b %d %H:%M:%S',
168 '%b %d %I:%M:%S%p',
168 '%b %d %I:%M:%S%p',
169 '%b %d %H:%M',
169 '%b %d %H:%M',
170 '%b %d %I:%M%p',
170 '%b %d %I:%M%p',
171 '%b %d %Y',
171 '%b %d %Y',
172 '%b %d',
172 '%b %d',
173 '%H:%M:%S',
173 '%H:%M:%S',
174 '%I:%M:%S%p',
174 '%I:%M:%S%p',
175 '%H:%M',
175 '%H:%M',
176 '%I:%M%p',
176 '%I:%M%p',
177 )
177 )
178
178
179 extendeddateformats = defaultdateformats + (
179 extendeddateformats = defaultdateformats + (
180 "%Y",
180 "%Y",
181 "%Y-%m",
181 "%Y-%m",
182 "%b",
182 "%b",
183 "%b %Y",
183 "%b %Y",
184 )
184 )
185
185
186 def cachefunc(func):
186 def cachefunc(func):
187 '''cache the result of function calls'''
187 '''cache the result of function calls'''
188 # XXX doesn't handle keywords args
188 # XXX doesn't handle keywords args
189 cache = {}
189 cache = {}
190 if func.func_code.co_argcount == 1:
190 if func.func_code.co_argcount == 1:
191 # we gain a small amount of time because
191 # we gain a small amount of time because
192 # we don't need to pack/unpack the list
192 # we don't need to pack/unpack the list
193 def f(arg):
193 def f(arg):
194 if arg not in cache:
194 if arg not in cache:
195 cache[arg] = func(arg)
195 cache[arg] = func(arg)
196 return cache[arg]
196 return cache[arg]
197 else:
197 else:
198 def f(*args):
198 def f(*args):
199 if args not in cache:
199 if args not in cache:
200 cache[args] = func(*args)
200 cache[args] = func(*args)
201 return cache[args]
201 return cache[args]
202
202
203 return f
203 return f
204
204
205 def lrucachefunc(func):
205 def lrucachefunc(func):
206 '''cache most recent results of function calls'''
206 '''cache most recent results of function calls'''
207 cache = {}
207 cache = {}
208 order = []
208 order = []
209 if func.func_code.co_argcount == 1:
209 if func.func_code.co_argcount == 1:
210 def f(arg):
210 def f(arg):
211 if arg not in cache:
211 if arg not in cache:
212 if len(cache) > 20:
212 if len(cache) > 20:
213 del cache[order.pop(0)]
213 del cache[order.pop(0)]
214 cache[arg] = func(arg)
214 cache[arg] = func(arg)
215 else:
215 else:
216 order.remove(arg)
216 order.remove(arg)
217 order.append(arg)
217 order.append(arg)
218 return cache[arg]
218 return cache[arg]
219 else:
219 else:
220 def f(*args):
220 def f(*args):
221 if args not in cache:
221 if args not in cache:
222 if len(cache) > 20:
222 if len(cache) > 20:
223 del cache[order.pop(0)]
223 del cache[order.pop(0)]
224 cache[args] = func(*args)
224 cache[args] = func(*args)
225 else:
225 else:
226 order.remove(args)
226 order.remove(args)
227 order.append(args)
227 order.append(args)
228 return cache[args]
228 return cache[args]
229
229
230 return f
230 return f
231
231
232 class propertycache(object):
232 class propertycache(object):
233 def __init__(self, func):
233 def __init__(self, func):
234 self.func = func
234 self.func = func
235 self.name = func.__name__
235 self.name = func.__name__
236 def __get__(self, obj, type=None):
236 def __get__(self, obj, type=None):
237 result = self.func(obj)
237 result = self.func(obj)
238 setattr(obj, self.name, result)
238 setattr(obj, self.name, result)
239 return result
239 return result
240
240
241 def pipefilter(s, cmd):
241 def pipefilter(s, cmd):
242 '''filter string S through command CMD, returning its output'''
242 '''filter string S through command CMD, returning its output'''
243 p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
243 p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
244 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
244 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
245 pout, perr = p.communicate(s)
245 pout, perr = p.communicate(s)
246 return pout
246 return pout
247
247
248 def tempfilter(s, cmd):
248 def tempfilter(s, cmd):
249 '''filter string S through a pair of temporary files with CMD.
249 '''filter string S through a pair of temporary files with CMD.
250 CMD is used as a template to create the real command to be run,
250 CMD is used as a template to create the real command to be run,
251 with the strings INFILE and OUTFILE replaced by the real names of
251 with the strings INFILE and OUTFILE replaced by the real names of
252 the temporary files generated.'''
252 the temporary files generated.'''
253 inname, outname = None, None
253 inname, outname = None, None
254 try:
254 try:
255 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
255 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
256 fp = os.fdopen(infd, 'wb')
256 fp = os.fdopen(infd, 'wb')
257 fp.write(s)
257 fp.write(s)
258 fp.close()
258 fp.close()
259 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
259 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
260 os.close(outfd)
260 os.close(outfd)
261 cmd = cmd.replace('INFILE', inname)
261 cmd = cmd.replace('INFILE', inname)
262 cmd = cmd.replace('OUTFILE', outname)
262 cmd = cmd.replace('OUTFILE', outname)
263 code = os.system(cmd)
263 code = os.system(cmd)
264 if sys.platform == 'OpenVMS' and code & 1:
264 if sys.platform == 'OpenVMS' and code & 1:
265 code = 0
265 code = 0
266 if code:
266 if code:
267 raise Abort(_("command '%s' failed: %s") %
267 raise Abort(_("command '%s' failed: %s") %
268 (cmd, explainexit(code)))
268 (cmd, explainexit(code)))
269 fp = open(outname, 'rb')
269 fp = open(outname, 'rb')
270 r = fp.read()
270 r = fp.read()
271 fp.close()
271 fp.close()
272 return r
272 return r
273 finally:
273 finally:
274 try:
274 try:
275 if inname:
275 if inname:
276 os.unlink(inname)
276 os.unlink(inname)
277 except OSError:
277 except OSError:
278 pass
278 pass
279 try:
279 try:
280 if outname:
280 if outname:
281 os.unlink(outname)
281 os.unlink(outname)
282 except OSError:
282 except OSError:
283 pass
283 pass
284
284
285 filtertable = {
285 filtertable = {
286 'tempfile:': tempfilter,
286 'tempfile:': tempfilter,
287 'pipe:': pipefilter,
287 'pipe:': pipefilter,
288 }
288 }
289
289
290 def filter(s, cmd):
290 def filter(s, cmd):
291 "filter a string through a command that transforms its input to its output"
291 "filter a string through a command that transforms its input to its output"
292 for name, fn in filtertable.iteritems():
292 for name, fn in filtertable.iteritems():
293 if cmd.startswith(name):
293 if cmd.startswith(name):
294 return fn(s, cmd[len(name):].lstrip())
294 return fn(s, cmd[len(name):].lstrip())
295 return pipefilter(s, cmd)
295 return pipefilter(s, cmd)
296
296
297 def binary(s):
297 def binary(s):
298 """return true if a string is binary data"""
298 """return true if a string is binary data"""
299 return bool(s and '\0' in s)
299 return bool(s and '\0' in s)
300
300
301 def increasingchunks(source, min=1024, max=65536):
301 def increasingchunks(source, min=1024, max=65536):
302 '''return no less than min bytes per chunk while data remains,
302 '''return no less than min bytes per chunk while data remains,
303 doubling min after each chunk until it reaches max'''
303 doubling min after each chunk until it reaches max'''
304 def log2(x):
304 def log2(x):
305 if not x:
305 if not x:
306 return 0
306 return 0
307 i = 0
307 i = 0
308 while x:
308 while x:
309 x >>= 1
309 x >>= 1
310 i += 1
310 i += 1
311 return i - 1
311 return i - 1
312
312
313 buf = []
313 buf = []
314 blen = 0
314 blen = 0
315 for chunk in source:
315 for chunk in source:
316 buf.append(chunk)
316 buf.append(chunk)
317 blen += len(chunk)
317 blen += len(chunk)
318 if blen >= min:
318 if blen >= min:
319 if min < max:
319 if min < max:
320 min = min << 1
320 min = min << 1
321 nmin = 1 << log2(blen)
321 nmin = 1 << log2(blen)
322 if nmin > min:
322 if nmin > min:
323 min = nmin
323 min = nmin
324 if min > max:
324 if min > max:
325 min = max
325 min = max
326 yield ''.join(buf)
326 yield ''.join(buf)
327 blen = 0
327 blen = 0
328 buf = []
328 buf = []
329 if buf:
329 if buf:
330 yield ''.join(buf)
330 yield ''.join(buf)
331
331
332 Abort = error.Abort
332 Abort = error.Abort
333
333
334 def always(fn):
334 def always(fn):
335 return True
335 return True
336
336
337 def never(fn):
337 def never(fn):
338 return False
338 return False
339
339
340 def pathto(root, n1, n2):
340 def pathto(root, n1, n2):
341 '''return the relative path from one place to another.
341 '''return the relative path from one place to another.
342 root should use os.sep to separate directories
342 root should use os.sep to separate directories
343 n1 should use os.sep to separate directories
343 n1 should use os.sep to separate directories
344 n2 should use "/" to separate directories
344 n2 should use "/" to separate directories
345 returns an os.sep-separated path.
345 returns an os.sep-separated path.
346
346
347 If n1 is a relative path, it's assumed it's
347 If n1 is a relative path, it's assumed it's
348 relative to root.
348 relative to root.
349 n2 should always be relative to root.
349 n2 should always be relative to root.
350 '''
350 '''
351 if not n1:
351 if not n1:
352 return localpath(n2)
352 return localpath(n2)
353 if os.path.isabs(n1):
353 if os.path.isabs(n1):
354 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
354 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
355 return os.path.join(root, localpath(n2))
355 return os.path.join(root, localpath(n2))
356 n2 = '/'.join((pconvert(root), n2))
356 n2 = '/'.join((pconvert(root), n2))
357 a, b = splitpath(n1), n2.split('/')
357 a, b = splitpath(n1), n2.split('/')
358 a.reverse()
358 a.reverse()
359 b.reverse()
359 b.reverse()
360 while a and b and a[-1] == b[-1]:
360 while a and b and a[-1] == b[-1]:
361 a.pop()
361 a.pop()
362 b.pop()
362 b.pop()
363 b.reverse()
363 b.reverse()
364 return os.sep.join((['..'] * len(a)) + b) or '.'
364 return os.sep.join((['..'] * len(a)) + b) or '.'
365
365
366 _hgexecutable = None
366 _hgexecutable = None
367
367
368 def mainfrozen():
368 def mainfrozen():
369 """return True if we are a frozen executable.
369 """return True if we are a frozen executable.
370
370
371 The code supports py2exe (most common, Windows only) and tools/freeze
371 The code supports py2exe (most common, Windows only) and tools/freeze
372 (portable, not much used).
372 (portable, not much used).
373 """
373 """
374 return (safehasattr(sys, "frozen") or # new py2exe
374 return (safehasattr(sys, "frozen") or # new py2exe
375 safehasattr(sys, "importers") or # old py2exe
375 safehasattr(sys, "importers") or # old py2exe
376 imp.is_frozen("__main__")) # tools/freeze
376 imp.is_frozen("__main__")) # tools/freeze
377
377
378 def hgexecutable():
378 def hgexecutable():
379 """return location of the 'hg' executable.
379 """return location of the 'hg' executable.
380
380
381 Defaults to $HG or 'hg' in the search path.
381 Defaults to $HG or 'hg' in the search path.
382 """
382 """
383 if _hgexecutable is None:
383 if _hgexecutable is None:
384 hg = os.environ.get('HG')
384 hg = os.environ.get('HG')
385 mainmod = sys.modules['__main__']
385 mainmod = sys.modules['__main__']
386 if hg:
386 if hg:
387 _sethgexecutable(hg)
387 _sethgexecutable(hg)
388 elif mainfrozen():
388 elif mainfrozen():
389 _sethgexecutable(sys.executable)
389 _sethgexecutable(sys.executable)
390 elif os.path.basename(getattr(mainmod, '__file__', '')) == 'hg':
390 elif os.path.basename(getattr(mainmod, '__file__', '')) == 'hg':
391 _sethgexecutable(mainmod.__file__)
391 _sethgexecutable(mainmod.__file__)
392 else:
392 else:
393 exe = findexe('hg') or os.path.basename(sys.argv[0])
393 exe = findexe('hg') or os.path.basename(sys.argv[0])
394 _sethgexecutable(exe)
394 _sethgexecutable(exe)
395 return _hgexecutable
395 return _hgexecutable
396
396
397 def _sethgexecutable(path):
397 def _sethgexecutable(path):
398 """set location of the 'hg' executable"""
398 """set location of the 'hg' executable"""
399 global _hgexecutable
399 global _hgexecutable
400 _hgexecutable = path
400 _hgexecutable = path
401
401
402 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None, out=None):
402 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None, out=None):
403 '''enhanced shell command execution.
403 '''enhanced shell command execution.
404 run with environment maybe modified, maybe in different dir.
404 run with environment maybe modified, maybe in different dir.
405
405
406 if command fails and onerr is None, return status. if ui object,
406 if command fails and onerr is None, return status. if ui object,
407 print error message and return status, else raise onerr object as
407 print error message and return status, else raise onerr object as
408 exception.
408 exception.
409
409
410 if out is specified, it is assumed to be a file-like object that has a
410 if out is specified, it is assumed to be a file-like object that has a
411 write() method. stdout and stderr will be redirected to out.'''
411 write() method. stdout and stderr will be redirected to out.'''
412 try:
412 try:
413 sys.stdout.flush()
413 sys.stdout.flush()
414 except Exception:
414 except Exception:
415 pass
415 pass
416 def py2shell(val):
416 def py2shell(val):
417 'convert python object into string that is useful to shell'
417 'convert python object into string that is useful to shell'
418 if val is None or val is False:
418 if val is None or val is False:
419 return '0'
419 return '0'
420 if val is True:
420 if val is True:
421 return '1'
421 return '1'
422 return str(val)
422 return str(val)
423 origcmd = cmd
423 origcmd = cmd
424 cmd = quotecommand(cmd)
424 cmd = quotecommand(cmd)
425 if sys.platform == 'plan9':
425 if sys.platform == 'plan9':
426 # subprocess kludge to work around issues in half-baked Python
426 # subprocess kludge to work around issues in half-baked Python
427 # ports, notably bichued/python:
427 # ports, notably bichued/python:
428 if not cwd is None:
428 if not cwd is None:
429 os.chdir(cwd)
429 os.chdir(cwd)
430 rc = os.system(cmd)
430 rc = os.system(cmd)
431 else:
431 else:
432 env = dict(os.environ)
432 env = dict(os.environ)
433 env.update((k, py2shell(v)) for k, v in environ.iteritems())
433 env.update((k, py2shell(v)) for k, v in environ.iteritems())
434 env['HG'] = hgexecutable()
434 env['HG'] = hgexecutable()
435 if out is None or out == sys.__stdout__:
435 if out is None or out == sys.__stdout__:
436 rc = subprocess.call(cmd, shell=True, close_fds=closefds,
436 rc = subprocess.call(cmd, shell=True, close_fds=closefds,
437 env=env, cwd=cwd)
437 env=env, cwd=cwd)
438 else:
438 else:
439 proc = subprocess.Popen(cmd, shell=True, close_fds=closefds,
439 proc = subprocess.Popen(cmd, shell=True, close_fds=closefds,
440 env=env, cwd=cwd, stdout=subprocess.PIPE,
440 env=env, cwd=cwd, stdout=subprocess.PIPE,
441 stderr=subprocess.STDOUT)
441 stderr=subprocess.STDOUT)
442 for line in proc.stdout:
442 for line in proc.stdout:
443 out.write(line)
443 out.write(line)
444 proc.wait()
444 proc.wait()
445 rc = proc.returncode
445 rc = proc.returncode
446 if sys.platform == 'OpenVMS' and rc & 1:
446 if sys.platform == 'OpenVMS' and rc & 1:
447 rc = 0
447 rc = 0
448 if rc and onerr:
448 if rc and onerr:
449 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
449 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
450 explainexit(rc)[0])
450 explainexit(rc)[0])
451 if errprefix:
451 if errprefix:
452 errmsg = '%s: %s' % (errprefix, errmsg)
452 errmsg = '%s: %s' % (errprefix, errmsg)
453 try:
453 try:
454 onerr.warn(errmsg + '\n')
454 onerr.warn(errmsg + '\n')
455 except AttributeError:
455 except AttributeError:
456 raise onerr(errmsg)
456 raise onerr(errmsg)
457 return rc
457 return rc
458
458
459 def checksignature(func):
459 def checksignature(func):
460 '''wrap a function with code to check for calling errors'''
460 '''wrap a function with code to check for calling errors'''
461 def check(*args, **kwargs):
461 def check(*args, **kwargs):
462 try:
462 try:
463 return func(*args, **kwargs)
463 return func(*args, **kwargs)
464 except TypeError:
464 except TypeError:
465 if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
465 if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
466 raise error.SignatureError
466 raise error.SignatureError
467 raise
467 raise
468
468
469 return check
469 return check
470
470
471 def copyfile(src, dest):
471 def copyfile(src, dest):
472 "copy a file, preserving mode and atime/mtime"
472 "copy a file, preserving mode and atime/mtime"
473 if os.path.islink(src):
473 if os.path.islink(src):
474 try:
474 try:
475 os.unlink(dest)
475 os.unlink(dest)
476 except OSError:
476 except OSError:
477 pass
477 pass
478 os.symlink(os.readlink(src), dest)
478 os.symlink(os.readlink(src), dest)
479 else:
479 else:
480 try:
480 try:
481 shutil.copyfile(src, dest)
481 shutil.copyfile(src, dest)
482 shutil.copymode(src, dest)
482 shutil.copymode(src, dest)
483 except shutil.Error, inst:
483 except shutil.Error, inst:
484 raise Abort(str(inst))
484 raise Abort(str(inst))
485
485
486 def copyfiles(src, dst, hardlink=None):
486 def copyfiles(src, dst, hardlink=None):
487 """Copy a directory tree using hardlinks if possible"""
487 """Copy a directory tree using hardlinks if possible"""
488
488
489 if hardlink is None:
489 if hardlink is None:
490 hardlink = (os.stat(src).st_dev ==
490 hardlink = (os.stat(src).st_dev ==
491 os.stat(os.path.dirname(dst)).st_dev)
491 os.stat(os.path.dirname(dst)).st_dev)
492
492
493 num = 0
493 num = 0
494 if os.path.isdir(src):
494 if os.path.isdir(src):
495 os.mkdir(dst)
495 os.mkdir(dst)
496 for name, kind in osutil.listdir(src):
496 for name, kind in osutil.listdir(src):
497 srcname = os.path.join(src, name)
497 srcname = os.path.join(src, name)
498 dstname = os.path.join(dst, name)
498 dstname = os.path.join(dst, name)
499 hardlink, n = copyfiles(srcname, dstname, hardlink)
499 hardlink, n = copyfiles(srcname, dstname, hardlink)
500 num += n
500 num += n
501 else:
501 else:
502 if hardlink:
502 if hardlink:
503 try:
503 try:
504 oslink(src, dst)
504 oslink(src, dst)
505 except (IOError, OSError):
505 except (IOError, OSError):
506 hardlink = False
506 hardlink = False
507 shutil.copy(src, dst)
507 shutil.copy(src, dst)
508 else:
508 else:
509 shutil.copy(src, dst)
509 shutil.copy(src, dst)
510 num += 1
510 num += 1
511
511
512 return hardlink, num
512 return hardlink, num
513
513
514 _winreservednames = '''con prn aux nul
514 _winreservednames = '''con prn aux nul
515 com1 com2 com3 com4 com5 com6 com7 com8 com9
515 com1 com2 com3 com4 com5 com6 com7 com8 com9
516 lpt1 lpt2 lpt3 lpt4 lpt5 lpt6 lpt7 lpt8 lpt9'''.split()
516 lpt1 lpt2 lpt3 lpt4 lpt5 lpt6 lpt7 lpt8 lpt9'''.split()
517 _winreservedchars = ':*?"<>|'
517 _winreservedchars = ':*?"<>|'
518 def checkwinfilename(path):
518 def checkwinfilename(path):
519 '''Check that the base-relative path is a valid filename on Windows.
519 '''Check that the base-relative path is a valid filename on Windows.
520 Returns None if the path is ok, or a UI string describing the problem.
520 Returns None if the path is ok, or a UI string describing the problem.
521
521
522 >>> checkwinfilename("just/a/normal/path")
522 >>> checkwinfilename("just/a/normal/path")
523 >>> checkwinfilename("foo/bar/con.xml")
523 >>> checkwinfilename("foo/bar/con.xml")
524 "filename contains 'con', which is reserved on Windows"
524 "filename contains 'con', which is reserved on Windows"
525 >>> checkwinfilename("foo/con.xml/bar")
525 >>> checkwinfilename("foo/con.xml/bar")
526 "filename contains 'con', which is reserved on Windows"
526 "filename contains 'con', which is reserved on Windows"
527 >>> checkwinfilename("foo/bar/xml.con")
527 >>> checkwinfilename("foo/bar/xml.con")
528 >>> checkwinfilename("foo/bar/AUX/bla.txt")
528 >>> checkwinfilename("foo/bar/AUX/bla.txt")
529 "filename contains 'AUX', which is reserved on Windows"
529 "filename contains 'AUX', which is reserved on Windows"
530 >>> checkwinfilename("foo/bar/bla:.txt")
530 >>> checkwinfilename("foo/bar/bla:.txt")
531 "filename contains ':', which is reserved on Windows"
531 "filename contains ':', which is reserved on Windows"
532 >>> checkwinfilename("foo/bar/b\07la.txt")
532 >>> checkwinfilename("foo/bar/b\07la.txt")
533 "filename contains '\\\\x07', which is invalid on Windows"
533 "filename contains '\\\\x07', which is invalid on Windows"
534 >>> checkwinfilename("foo/bar/bla ")
534 >>> checkwinfilename("foo/bar/bla ")
535 "filename ends with ' ', which is not allowed on Windows"
535 "filename ends with ' ', which is not allowed on Windows"
536 >>> checkwinfilename("../bar")
536 >>> checkwinfilename("../bar")
537 '''
537 '''
538 for n in path.replace('\\', '/').split('/'):
538 for n in path.replace('\\', '/').split('/'):
539 if not n:
539 if not n:
540 continue
540 continue
541 for c in n:
541 for c in n:
542 if c in _winreservedchars:
542 if c in _winreservedchars:
543 return _("filename contains '%s', which is reserved "
543 return _("filename contains '%s', which is reserved "
544 "on Windows") % c
544 "on Windows") % c
545 if ord(c) <= 31:
545 if ord(c) <= 31:
546 return _("filename contains %r, which is invalid "
546 return _("filename contains %r, which is invalid "
547 "on Windows") % c
547 "on Windows") % c
548 base = n.split('.')[0]
548 base = n.split('.')[0]
549 if base and base.lower() in _winreservednames:
549 if base and base.lower() in _winreservednames:
550 return _("filename contains '%s', which is reserved "
550 return _("filename contains '%s', which is reserved "
551 "on Windows") % base
551 "on Windows") % base
552 t = n[-1]
552 t = n[-1]
553 if t in '. ' and n not in '..':
553 if t in '. ' and n not in '..':
554 return _("filename ends with '%s', which is not allowed "
554 return _("filename ends with '%s', which is not allowed "
555 "on Windows") % t
555 "on Windows") % t
556
556
557 if os.name == 'nt':
557 if os.name == 'nt':
558 checkosfilename = checkwinfilename
558 checkosfilename = checkwinfilename
559 else:
559 else:
560 checkosfilename = platform.checkosfilename
560 checkosfilename = platform.checkosfilename
561
561
562 def makelock(info, pathname):
562 def makelock(info, pathname):
563 try:
563 try:
564 return os.symlink(info, pathname)
564 return os.symlink(info, pathname)
565 except OSError, why:
565 except OSError, why:
566 if why.errno == errno.EEXIST:
566 if why.errno == errno.EEXIST:
567 raise
567 raise
568 except AttributeError: # no symlink in os
568 except AttributeError: # no symlink in os
569 pass
569 pass
570
570
571 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
571 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
572 os.write(ld, info)
572 os.write(ld, info)
573 os.close(ld)
573 os.close(ld)
574
574
575 def readlock(pathname):
575 def readlock(pathname):
576 try:
576 try:
577 return os.readlink(pathname)
577 return os.readlink(pathname)
578 except OSError, why:
578 except OSError, why:
579 if why.errno not in (errno.EINVAL, errno.ENOSYS):
579 if why.errno not in (errno.EINVAL, errno.ENOSYS):
580 raise
580 raise
581 except AttributeError: # no symlink in os
581 except AttributeError: # no symlink in os
582 pass
582 pass
583 fp = posixfile(pathname)
583 fp = posixfile(pathname)
584 r = fp.read()
584 r = fp.read()
585 fp.close()
585 fp.close()
586 return r
586 return r
587
587
588 def fstat(fp):
588 def fstat(fp):
589 '''stat file object that may not have fileno method.'''
589 '''stat file object that may not have fileno method.'''
590 try:
590 try:
591 return os.fstat(fp.fileno())
591 return os.fstat(fp.fileno())
592 except AttributeError:
592 except AttributeError:
593 return os.stat(fp.name)
593 return os.stat(fp.name)
594
594
595 # File system features
595 # File system features
596
596
597 def checkcase(path):
597 def checkcase(path):
598 """
598 """
599 Check whether the given path is on a case-sensitive filesystem
599 Check whether the given path is on a case-sensitive filesystem
600
600
601 Requires a path (like /foo/.hg) ending with a foldable final
601 Requires a path (like /foo/.hg) ending with a foldable final
602 directory component.
602 directory component.
603 """
603 """
604 s1 = os.stat(path)
604 s1 = os.stat(path)
605 d, b = os.path.split(path)
605 d, b = os.path.split(path)
606 b2 = b.upper()
606 b2 = b.upper()
607 if b == b2:
607 if b == b2:
608 b2 = b.lower()
608 b2 = b.lower()
609 if b == b2:
609 if b == b2:
610 return True # no evidence against case sensitivity
610 return True # no evidence against case sensitivity
611 p2 = os.path.join(d, b2)
611 p2 = os.path.join(d, b2)
612 try:
612 try:
613 s2 = os.stat(p2)
613 s2 = os.stat(p2)
614 if s2 == s1:
614 if s2 == s1:
615 return False
615 return False
616 return True
616 return True
617 except OSError:
617 except OSError:
618 return True
618 return True
619
619
620 _fspathcache = {}
620 _fspathcache = {}
621 def fspath(name, root):
621 def fspath(name, root):
622 '''Get name in the case stored in the filesystem
622 '''Get name in the case stored in the filesystem
623
623
624 The name should be relative to root, and be normcase-ed for efficiency.
624 The name should be relative to root, and be normcase-ed for efficiency.
625
625
626 Note that this function is unnecessary, and should not be
626 Note that this function is unnecessary, and should not be
627 called, for case-sensitive filesystems (simply because it's expensive).
627 called, for case-sensitive filesystems (simply because it's expensive).
628
628
629 The root should be normcase-ed, too.
629 The root should be normcase-ed, too.
630 '''
630 '''
631 def find(p, contents):
631 def find(p, contents):
632 for n in contents:
632 for n in contents:
633 if normcase(n) == p:
633 if normcase(n) == p:
634 return n
634 return n
635 return None
635 return None
636
636
637 seps = os.sep
637 seps = os.sep
638 if os.altsep:
638 if os.altsep:
639 seps = seps + os.altsep
639 seps = seps + os.altsep
640 # Protect backslashes. This gets silly very quickly.
640 # Protect backslashes. This gets silly very quickly.
641 seps.replace('\\','\\\\')
641 seps.replace('\\','\\\\')
642 pattern = re.compile(r'([^%s]+)|([%s]+)' % (seps, seps))
642 pattern = re.compile(r'([^%s]+)|([%s]+)' % (seps, seps))
643 dir = os.path.normpath(root)
643 dir = os.path.normpath(root)
644 result = []
644 result = []
645 for part, sep in pattern.findall(name):
645 for part, sep in pattern.findall(name):
646 if sep:
646 if sep:
647 result.append(sep)
647 result.append(sep)
648 continue
648 continue
649
649
650 if dir not in _fspathcache:
650 if dir not in _fspathcache:
651 _fspathcache[dir] = os.listdir(dir)
651 _fspathcache[dir] = os.listdir(dir)
652 contents = _fspathcache[dir]
652 contents = _fspathcache[dir]
653
653
654 found = find(part, contents)
654 found = find(part, contents)
655 if not found:
655 if not found:
656 # retry "once per directory" per "dirstate.walk" which
656 # retry "once per directory" per "dirstate.walk" which
657 # may take place for each patches of "hg qpush", for example
657 # may take place for each patches of "hg qpush", for example
658 contents = os.listdir(dir)
658 contents = os.listdir(dir)
659 _fspathcache[dir] = contents
659 _fspathcache[dir] = contents
660 found = find(part, contents)
660 found = find(part, contents)
661
661
662 result.append(found or part)
662 result.append(found or part)
663 dir = os.path.join(dir, part)
663 dir = os.path.join(dir, part)
664
664
665 return ''.join(result)
665 return ''.join(result)
666
666
667 def checknlink(testfile):
667 def checknlink(testfile):
668 '''check whether hardlink count reporting works properly'''
668 '''check whether hardlink count reporting works properly'''
669
669
670 # testfile may be open, so we need a separate file for checking to
670 # testfile may be open, so we need a separate file for checking to
671 # work around issue2543 (or testfile may get lost on Samba shares)
671 # work around issue2543 (or testfile may get lost on Samba shares)
672 f1 = testfile + ".hgtmp1"
672 f1 = testfile + ".hgtmp1"
673 if os.path.lexists(f1):
673 if os.path.lexists(f1):
674 return False
674 return False
675 try:
675 try:
676 posixfile(f1, 'w').close()
676 posixfile(f1, 'w').close()
677 except IOError:
677 except IOError:
678 return False
678 return False
679
679
680 f2 = testfile + ".hgtmp2"
680 f2 = testfile + ".hgtmp2"
681 fd = None
681 fd = None
682 try:
682 try:
683 try:
683 try:
684 oslink(f1, f2)
684 oslink(f1, f2)
685 except OSError:
685 except OSError:
686 return False
686 return False
687
687
688 # nlinks() may behave differently for files on Windows shares if
688 # nlinks() may behave differently for files on Windows shares if
689 # the file is open.
689 # the file is open.
690 fd = posixfile(f2)
690 fd = posixfile(f2)
691 return nlinks(f2) > 1
691 return nlinks(f2) > 1
692 finally:
692 finally:
693 if fd is not None:
693 if fd is not None:
694 fd.close()
694 fd.close()
695 for f in (f1, f2):
695 for f in (f1, f2):
696 try:
696 try:
697 os.unlink(f)
697 os.unlink(f)
698 except OSError:
698 except OSError:
699 pass
699 pass
700
700
701 return False
701 return False
702
702
703 def endswithsep(path):
703 def endswithsep(path):
704 '''Check path ends with os.sep or os.altsep.'''
704 '''Check path ends with os.sep or os.altsep.'''
705 return path.endswith(os.sep) or os.altsep and path.endswith(os.altsep)
705 return path.endswith(os.sep) or os.altsep and path.endswith(os.altsep)
706
706
707 def splitpath(path):
707 def splitpath(path):
708 '''Split path by os.sep.
708 '''Split path by os.sep.
709 Note that this function does not use os.altsep because this is
709 Note that this function does not use os.altsep because this is
710 an alternative of simple "xxx.split(os.sep)".
710 an alternative of simple "xxx.split(os.sep)".
711 It is recommended to use os.path.normpath() before using this
711 It is recommended to use os.path.normpath() before using this
712 function if need.'''
712 function if need.'''
713 return path.split(os.sep)
713 return path.split(os.sep)
714
714
715 def gui():
715 def gui():
716 '''Are we running in a GUI?'''
716 '''Are we running in a GUI?'''
717 if sys.platform == 'darwin':
717 if sys.platform == 'darwin':
718 if 'SSH_CONNECTION' in os.environ:
718 if 'SSH_CONNECTION' in os.environ:
719 # handle SSH access to a box where the user is logged in
719 # handle SSH access to a box where the user is logged in
720 return False
720 return False
721 elif getattr(osutil, 'isgui', None):
721 elif getattr(osutil, 'isgui', None):
722 # check if a CoreGraphics session is available
722 # check if a CoreGraphics session is available
723 return osutil.isgui()
723 return osutil.isgui()
724 else:
724 else:
725 # pure build; use a safe default
725 # pure build; use a safe default
726 return True
726 return True
727 else:
727 else:
728 return os.name == "nt" or os.environ.get("DISPLAY")
728 return os.name == "nt" or os.environ.get("DISPLAY")
729
729
730 def mktempcopy(name, emptyok=False, createmode=None):
730 def mktempcopy(name, emptyok=False, createmode=None):
731 """Create a temporary file with the same contents from name
731 """Create a temporary file with the same contents from name
732
732
733 The permission bits are copied from the original file.
733 The permission bits are copied from the original file.
734
734
735 If the temporary file is going to be truncated immediately, you
735 If the temporary file is going to be truncated immediately, you
736 can use emptyok=True as an optimization.
736 can use emptyok=True as an optimization.
737
737
738 Returns the name of the temporary file.
738 Returns the name of the temporary file.
739 """
739 """
740 d, fn = os.path.split(name)
740 d, fn = os.path.split(name)
741 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
741 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
742 os.close(fd)
742 os.close(fd)
743 # Temporary files are created with mode 0600, which is usually not
743 # Temporary files are created with mode 0600, which is usually not
744 # what we want. If the original file already exists, just copy
744 # what we want. If the original file already exists, just copy
745 # its mode. Otherwise, manually obey umask.
745 # its mode. Otherwise, manually obey umask.
746 copymode(name, temp, createmode)
746 copymode(name, temp, createmode)
747 if emptyok:
747 if emptyok:
748 return temp
748 return temp
749 try:
749 try:
750 try:
750 try:
751 ifp = posixfile(name, "rb")
751 ifp = posixfile(name, "rb")
752 except IOError, inst:
752 except IOError, inst:
753 if inst.errno == errno.ENOENT:
753 if inst.errno == errno.ENOENT:
754 return temp
754 return temp
755 if not getattr(inst, 'filename', None):
755 if not getattr(inst, 'filename', None):
756 inst.filename = name
756 inst.filename = name
757 raise
757 raise
758 ofp = posixfile(temp, "wb")
758 ofp = posixfile(temp, "wb")
759 for chunk in filechunkiter(ifp):
759 for chunk in filechunkiter(ifp):
760 ofp.write(chunk)
760 ofp.write(chunk)
761 ifp.close()
761 ifp.close()
762 ofp.close()
762 ofp.close()
763 except:
763 except:
764 try: os.unlink(temp)
764 try: os.unlink(temp)
765 except: pass
765 except OSError: pass
766 raise
766 raise
767 return temp
767 return temp
768
768
769 class atomictempfile(object):
769 class atomictempfile(object):
770 '''writeable file object that atomically updates a file
770 '''writeable file object that atomically updates a file
771
771
772 All writes will go to a temporary copy of the original file. Call
772 All writes will go to a temporary copy of the original file. Call
773 close() when you are done writing, and atomictempfile will rename
773 close() when you are done writing, and atomictempfile will rename
774 the temporary copy to the original name, making the changes
774 the temporary copy to the original name, making the changes
775 visible. If the object is destroyed without being closed, all your
775 visible. If the object is destroyed without being closed, all your
776 writes are discarded.
776 writes are discarded.
777 '''
777 '''
778 def __init__(self, name, mode='w+b', createmode=None):
778 def __init__(self, name, mode='w+b', createmode=None):
779 self.__name = name # permanent name
779 self.__name = name # permanent name
780 self._tempname = mktempcopy(name, emptyok=('w' in mode),
780 self._tempname = mktempcopy(name, emptyok=('w' in mode),
781 createmode=createmode)
781 createmode=createmode)
782 self._fp = posixfile(self._tempname, mode)
782 self._fp = posixfile(self._tempname, mode)
783
783
784 # delegated methods
784 # delegated methods
785 self.write = self._fp.write
785 self.write = self._fp.write
786 self.fileno = self._fp.fileno
786 self.fileno = self._fp.fileno
787
787
788 def close(self):
788 def close(self):
789 if not self._fp.closed:
789 if not self._fp.closed:
790 self._fp.close()
790 self._fp.close()
791 rename(self._tempname, localpath(self.__name))
791 rename(self._tempname, localpath(self.__name))
792
792
793 def discard(self):
793 def discard(self):
794 if not self._fp.closed:
794 if not self._fp.closed:
795 try:
795 try:
796 os.unlink(self._tempname)
796 os.unlink(self._tempname)
797 except OSError:
797 except OSError:
798 pass
798 pass
799 self._fp.close()
799 self._fp.close()
800
800
801 def __del__(self):
801 def __del__(self):
802 if safehasattr(self, '_fp'): # constructor actually did something
802 if safehasattr(self, '_fp'): # constructor actually did something
803 self.discard()
803 self.discard()
804
804
805 def makedirs(name, mode=None):
805 def makedirs(name, mode=None):
806 """recursive directory creation with parent mode inheritance"""
806 """recursive directory creation with parent mode inheritance"""
807 try:
807 try:
808 os.mkdir(name)
808 os.mkdir(name)
809 except OSError, err:
809 except OSError, err:
810 if err.errno == errno.EEXIST:
810 if err.errno == errno.EEXIST:
811 return
811 return
812 if err.errno != errno.ENOENT or not name:
812 if err.errno != errno.ENOENT or not name:
813 raise
813 raise
814 parent = os.path.dirname(os.path.abspath(name))
814 parent = os.path.dirname(os.path.abspath(name))
815 if parent == name:
815 if parent == name:
816 raise
816 raise
817 makedirs(parent, mode)
817 makedirs(parent, mode)
818 os.mkdir(name)
818 os.mkdir(name)
819 if mode is not None:
819 if mode is not None:
820 os.chmod(name, mode)
820 os.chmod(name, mode)
821
821
822 def readfile(path):
822 def readfile(path):
823 fp = open(path, 'rb')
823 fp = open(path, 'rb')
824 try:
824 try:
825 return fp.read()
825 return fp.read()
826 finally:
826 finally:
827 fp.close()
827 fp.close()
828
828
829 def writefile(path, text):
829 def writefile(path, text):
830 fp = open(path, 'wb')
830 fp = open(path, 'wb')
831 try:
831 try:
832 fp.write(text)
832 fp.write(text)
833 finally:
833 finally:
834 fp.close()
834 fp.close()
835
835
836 def appendfile(path, text):
836 def appendfile(path, text):
837 fp = open(path, 'ab')
837 fp = open(path, 'ab')
838 try:
838 try:
839 fp.write(text)
839 fp.write(text)
840 finally:
840 finally:
841 fp.close()
841 fp.close()
842
842
843 class chunkbuffer(object):
843 class chunkbuffer(object):
844 """Allow arbitrary sized chunks of data to be efficiently read from an
844 """Allow arbitrary sized chunks of data to be efficiently read from an
845 iterator over chunks of arbitrary size."""
845 iterator over chunks of arbitrary size."""
846
846
847 def __init__(self, in_iter):
847 def __init__(self, in_iter):
848 """in_iter is the iterator that's iterating over the input chunks.
848 """in_iter is the iterator that's iterating over the input chunks.
849 targetsize is how big a buffer to try to maintain."""
849 targetsize is how big a buffer to try to maintain."""
850 def splitbig(chunks):
850 def splitbig(chunks):
851 for chunk in chunks:
851 for chunk in chunks:
852 if len(chunk) > 2**20:
852 if len(chunk) > 2**20:
853 pos = 0
853 pos = 0
854 while pos < len(chunk):
854 while pos < len(chunk):
855 end = pos + 2 ** 18
855 end = pos + 2 ** 18
856 yield chunk[pos:end]
856 yield chunk[pos:end]
857 pos = end
857 pos = end
858 else:
858 else:
859 yield chunk
859 yield chunk
860 self.iter = splitbig(in_iter)
860 self.iter = splitbig(in_iter)
861 self._queue = []
861 self._queue = []
862
862
863 def read(self, l):
863 def read(self, l):
864 """Read L bytes of data from the iterator of chunks of data.
864 """Read L bytes of data from the iterator of chunks of data.
865 Returns less than L bytes if the iterator runs dry."""
865 Returns less than L bytes if the iterator runs dry."""
866 left = l
866 left = l
867 buf = ''
867 buf = ''
868 queue = self._queue
868 queue = self._queue
869 while left > 0:
869 while left > 0:
870 # refill the queue
870 # refill the queue
871 if not queue:
871 if not queue:
872 target = 2**18
872 target = 2**18
873 for chunk in self.iter:
873 for chunk in self.iter:
874 queue.append(chunk)
874 queue.append(chunk)
875 target -= len(chunk)
875 target -= len(chunk)
876 if target <= 0:
876 if target <= 0:
877 break
877 break
878 if not queue:
878 if not queue:
879 break
879 break
880
880
881 chunk = queue.pop(0)
881 chunk = queue.pop(0)
882 left -= len(chunk)
882 left -= len(chunk)
883 if left < 0:
883 if left < 0:
884 queue.insert(0, chunk[left:])
884 queue.insert(0, chunk[left:])
885 buf += chunk[:left]
885 buf += chunk[:left]
886 else:
886 else:
887 buf += chunk
887 buf += chunk
888
888
889 return buf
889 return buf
890
890
891 def filechunkiter(f, size=65536, limit=None):
891 def filechunkiter(f, size=65536, limit=None):
892 """Create a generator that produces the data in the file size
892 """Create a generator that produces the data in the file size
893 (default 65536) bytes at a time, up to optional limit (default is
893 (default 65536) bytes at a time, up to optional limit (default is
894 to read all data). Chunks may be less than size bytes if the
894 to read all data). Chunks may be less than size bytes if the
895 chunk is the last chunk in the file, or the file is a socket or
895 chunk is the last chunk in the file, or the file is a socket or
896 some other type of file that sometimes reads less data than is
896 some other type of file that sometimes reads less data than is
897 requested."""
897 requested."""
898 assert size >= 0
898 assert size >= 0
899 assert limit is None or limit >= 0
899 assert limit is None or limit >= 0
900 while True:
900 while True:
901 if limit is None:
901 if limit is None:
902 nbytes = size
902 nbytes = size
903 else:
903 else:
904 nbytes = min(limit, size)
904 nbytes = min(limit, size)
905 s = nbytes and f.read(nbytes)
905 s = nbytes and f.read(nbytes)
906 if not s:
906 if not s:
907 break
907 break
908 if limit:
908 if limit:
909 limit -= len(s)
909 limit -= len(s)
910 yield s
910 yield s
911
911
912 def makedate():
912 def makedate():
913 ct = time.time()
913 ct = time.time()
914 if ct < 0:
914 if ct < 0:
915 hint = _("check your clock")
915 hint = _("check your clock")
916 raise Abort(_("negative timestamp: %d") % ct, hint=hint)
916 raise Abort(_("negative timestamp: %d") % ct, hint=hint)
917 delta = (datetime.datetime.utcfromtimestamp(ct) -
917 delta = (datetime.datetime.utcfromtimestamp(ct) -
918 datetime.datetime.fromtimestamp(ct))
918 datetime.datetime.fromtimestamp(ct))
919 tz = delta.days * 86400 + delta.seconds
919 tz = delta.days * 86400 + delta.seconds
920 return ct, tz
920 return ct, tz
921
921
922 def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
922 def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
923 """represent a (unixtime, offset) tuple as a localized time.
923 """represent a (unixtime, offset) tuple as a localized time.
924 unixtime is seconds since the epoch, and offset is the time zone's
924 unixtime is seconds since the epoch, and offset is the time zone's
925 number of seconds away from UTC. if timezone is false, do not
925 number of seconds away from UTC. if timezone is false, do not
926 append time zone to string."""
926 append time zone to string."""
927 t, tz = date or makedate()
927 t, tz = date or makedate()
928 if t < 0:
928 if t < 0:
929 t = 0 # time.gmtime(lt) fails on Windows for lt < -43200
929 t = 0 # time.gmtime(lt) fails on Windows for lt < -43200
930 tz = 0
930 tz = 0
931 if "%1" in format or "%2" in format:
931 if "%1" in format or "%2" in format:
932 sign = (tz > 0) and "-" or "+"
932 sign = (tz > 0) and "-" or "+"
933 minutes = abs(tz) // 60
933 minutes = abs(tz) // 60
934 format = format.replace("%1", "%c%02d" % (sign, minutes // 60))
934 format = format.replace("%1", "%c%02d" % (sign, minutes // 60))
935 format = format.replace("%2", "%02d" % (minutes % 60))
935 format = format.replace("%2", "%02d" % (minutes % 60))
936 try:
936 try:
937 t = time.gmtime(float(t) - tz)
937 t = time.gmtime(float(t) - tz)
938 except ValueError:
938 except ValueError:
939 # time was out of range
939 # time was out of range
940 t = time.gmtime(sys.maxint)
940 t = time.gmtime(sys.maxint)
941 s = time.strftime(format, t)
941 s = time.strftime(format, t)
942 return s
942 return s
943
943
944 def shortdate(date=None):
944 def shortdate(date=None):
945 """turn (timestamp, tzoff) tuple into iso 8631 date."""
945 """turn (timestamp, tzoff) tuple into iso 8631 date."""
946 return datestr(date, format='%Y-%m-%d')
946 return datestr(date, format='%Y-%m-%d')
947
947
948 def strdate(string, format, defaults=[]):
948 def strdate(string, format, defaults=[]):
949 """parse a localized time string and return a (unixtime, offset) tuple.
949 """parse a localized time string and return a (unixtime, offset) tuple.
950 if the string cannot be parsed, ValueError is raised."""
950 if the string cannot be parsed, ValueError is raised."""
951 def timezone(string):
951 def timezone(string):
952 tz = string.split()[-1]
952 tz = string.split()[-1]
953 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
953 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
954 sign = (tz[0] == "+") and 1 or -1
954 sign = (tz[0] == "+") and 1 or -1
955 hours = int(tz[1:3])
955 hours = int(tz[1:3])
956 minutes = int(tz[3:5])
956 minutes = int(tz[3:5])
957 return -sign * (hours * 60 + minutes) * 60
957 return -sign * (hours * 60 + minutes) * 60
958 if tz == "GMT" or tz == "UTC":
958 if tz == "GMT" or tz == "UTC":
959 return 0
959 return 0
960 return None
960 return None
961
961
962 # NOTE: unixtime = localunixtime + offset
962 # NOTE: unixtime = localunixtime + offset
963 offset, date = timezone(string), string
963 offset, date = timezone(string), string
964 if offset is not None:
964 if offset is not None:
965 date = " ".join(string.split()[:-1])
965 date = " ".join(string.split()[:-1])
966
966
967 # add missing elements from defaults
967 # add missing elements from defaults
968 usenow = False # default to using biased defaults
968 usenow = False # default to using biased defaults
969 for part in ("S", "M", "HI", "d", "mb", "yY"): # decreasing specificity
969 for part in ("S", "M", "HI", "d", "mb", "yY"): # decreasing specificity
970 found = [True for p in part if ("%"+p) in format]
970 found = [True for p in part if ("%"+p) in format]
971 if not found:
971 if not found:
972 date += "@" + defaults[part][usenow]
972 date += "@" + defaults[part][usenow]
973 format += "@%" + part[0]
973 format += "@%" + part[0]
974 else:
974 else:
975 # We've found a specific time element, less specific time
975 # We've found a specific time element, less specific time
976 # elements are relative to today
976 # elements are relative to today
977 usenow = True
977 usenow = True
978
978
979 timetuple = time.strptime(date, format)
979 timetuple = time.strptime(date, format)
980 localunixtime = int(calendar.timegm(timetuple))
980 localunixtime = int(calendar.timegm(timetuple))
981 if offset is None:
981 if offset is None:
982 # local timezone
982 # local timezone
983 unixtime = int(time.mktime(timetuple))
983 unixtime = int(time.mktime(timetuple))
984 offset = unixtime - localunixtime
984 offset = unixtime - localunixtime
985 else:
985 else:
986 unixtime = localunixtime + offset
986 unixtime = localunixtime + offset
987 return unixtime, offset
987 return unixtime, offset
988
988
989 def parsedate(date, formats=None, bias={}):
989 def parsedate(date, formats=None, bias={}):
990 """parse a localized date/time and return a (unixtime, offset) tuple.
990 """parse a localized date/time and return a (unixtime, offset) tuple.
991
991
992 The date may be a "unixtime offset" string or in one of the specified
992 The date may be a "unixtime offset" string or in one of the specified
993 formats. If the date already is a (unixtime, offset) tuple, it is returned.
993 formats. If the date already is a (unixtime, offset) tuple, it is returned.
994 """
994 """
995 if not date:
995 if not date:
996 return 0, 0
996 return 0, 0
997 if isinstance(date, tuple) and len(date) == 2:
997 if isinstance(date, tuple) and len(date) == 2:
998 return date
998 return date
999 if not formats:
999 if not formats:
1000 formats = defaultdateformats
1000 formats = defaultdateformats
1001 date = date.strip()
1001 date = date.strip()
1002 try:
1002 try:
1003 when, offset = map(int, date.split(' '))
1003 when, offset = map(int, date.split(' '))
1004 except ValueError:
1004 except ValueError:
1005 # fill out defaults
1005 # fill out defaults
1006 now = makedate()
1006 now = makedate()
1007 defaults = {}
1007 defaults = {}
1008 for part in ("d", "mb", "yY", "HI", "M", "S"):
1008 for part in ("d", "mb", "yY", "HI", "M", "S"):
1009 # this piece is for rounding the specific end of unknowns
1009 # this piece is for rounding the specific end of unknowns
1010 b = bias.get(part)
1010 b = bias.get(part)
1011 if b is None:
1011 if b is None:
1012 if part[0] in "HMS":
1012 if part[0] in "HMS":
1013 b = "00"
1013 b = "00"
1014 else:
1014 else:
1015 b = "0"
1015 b = "0"
1016
1016
1017 # this piece is for matching the generic end to today's date
1017 # this piece is for matching the generic end to today's date
1018 n = datestr(now, "%" + part[0])
1018 n = datestr(now, "%" + part[0])
1019
1019
1020 defaults[part] = (b, n)
1020 defaults[part] = (b, n)
1021
1021
1022 for format in formats:
1022 for format in formats:
1023 try:
1023 try:
1024 when, offset = strdate(date, format, defaults)
1024 when, offset = strdate(date, format, defaults)
1025 except (ValueError, OverflowError):
1025 except (ValueError, OverflowError):
1026 pass
1026 pass
1027 else:
1027 else:
1028 break
1028 break
1029 else:
1029 else:
1030 raise Abort(_('invalid date: %r') % date)
1030 raise Abort(_('invalid date: %r') % date)
1031 # validate explicit (probably user-specified) date and
1031 # validate explicit (probably user-specified) date and
1032 # time zone offset. values must fit in signed 32 bits for
1032 # time zone offset. values must fit in signed 32 bits for
1033 # current 32-bit linux runtimes. timezones go from UTC-12
1033 # current 32-bit linux runtimes. timezones go from UTC-12
1034 # to UTC+14
1034 # to UTC+14
1035 if abs(when) > 0x7fffffff:
1035 if abs(when) > 0x7fffffff:
1036 raise Abort(_('date exceeds 32 bits: %d') % when)
1036 raise Abort(_('date exceeds 32 bits: %d') % when)
1037 if when < 0:
1037 if when < 0:
1038 raise Abort(_('negative date value: %d') % when)
1038 raise Abort(_('negative date value: %d') % when)
1039 if offset < -50400 or offset > 43200:
1039 if offset < -50400 or offset > 43200:
1040 raise Abort(_('impossible time zone offset: %d') % offset)
1040 raise Abort(_('impossible time zone offset: %d') % offset)
1041 return when, offset
1041 return when, offset
1042
1042
1043 def matchdate(date):
1043 def matchdate(date):
1044 """Return a function that matches a given date match specifier
1044 """Return a function that matches a given date match specifier
1045
1045
1046 Formats include:
1046 Formats include:
1047
1047
1048 '{date}' match a given date to the accuracy provided
1048 '{date}' match a given date to the accuracy provided
1049
1049
1050 '<{date}' on or before a given date
1050 '<{date}' on or before a given date
1051
1051
1052 '>{date}' on or after a given date
1052 '>{date}' on or after a given date
1053
1053
1054 >>> p1 = parsedate("10:29:59")
1054 >>> p1 = parsedate("10:29:59")
1055 >>> p2 = parsedate("10:30:00")
1055 >>> p2 = parsedate("10:30:00")
1056 >>> p3 = parsedate("10:30:59")
1056 >>> p3 = parsedate("10:30:59")
1057 >>> p4 = parsedate("10:31:00")
1057 >>> p4 = parsedate("10:31:00")
1058 >>> p5 = parsedate("Sep 15 10:30:00 1999")
1058 >>> p5 = parsedate("Sep 15 10:30:00 1999")
1059 >>> f = matchdate("10:30")
1059 >>> f = matchdate("10:30")
1060 >>> f(p1[0])
1060 >>> f(p1[0])
1061 False
1061 False
1062 >>> f(p2[0])
1062 >>> f(p2[0])
1063 True
1063 True
1064 >>> f(p3[0])
1064 >>> f(p3[0])
1065 True
1065 True
1066 >>> f(p4[0])
1066 >>> f(p4[0])
1067 False
1067 False
1068 >>> f(p5[0])
1068 >>> f(p5[0])
1069 False
1069 False
1070 """
1070 """
1071
1071
1072 def lower(date):
1072 def lower(date):
1073 d = dict(mb="1", d="1")
1073 d = dict(mb="1", d="1")
1074 return parsedate(date, extendeddateformats, d)[0]
1074 return parsedate(date, extendeddateformats, d)[0]
1075
1075
1076 def upper(date):
1076 def upper(date):
1077 d = dict(mb="12", HI="23", M="59", S="59")
1077 d = dict(mb="12", HI="23", M="59", S="59")
1078 for days in ("31", "30", "29"):
1078 for days in ("31", "30", "29"):
1079 try:
1079 try:
1080 d["d"] = days
1080 d["d"] = days
1081 return parsedate(date, extendeddateformats, d)[0]
1081 return parsedate(date, extendeddateformats, d)[0]
1082 except Abort:
1082 except Abort:
1083 pass
1083 pass
1084 d["d"] = "28"
1084 d["d"] = "28"
1085 return parsedate(date, extendeddateformats, d)[0]
1085 return parsedate(date, extendeddateformats, d)[0]
1086
1086
1087 date = date.strip()
1087 date = date.strip()
1088
1088
1089 if not date:
1089 if not date:
1090 raise Abort(_("dates cannot consist entirely of whitespace"))
1090 raise Abort(_("dates cannot consist entirely of whitespace"))
1091 elif date[0] == "<":
1091 elif date[0] == "<":
1092 if not date[1:]:
1092 if not date[1:]:
1093 raise Abort(_("invalid day spec, use '<DATE'"))
1093 raise Abort(_("invalid day spec, use '<DATE'"))
1094 when = upper(date[1:])
1094 when = upper(date[1:])
1095 return lambda x: x <= when
1095 return lambda x: x <= when
1096 elif date[0] == ">":
1096 elif date[0] == ">":
1097 if not date[1:]:
1097 if not date[1:]:
1098 raise Abort(_("invalid day spec, use '>DATE'"))
1098 raise Abort(_("invalid day spec, use '>DATE'"))
1099 when = lower(date[1:])
1099 when = lower(date[1:])
1100 return lambda x: x >= when
1100 return lambda x: x >= when
1101 elif date[0] == "-":
1101 elif date[0] == "-":
1102 try:
1102 try:
1103 days = int(date[1:])
1103 days = int(date[1:])
1104 except ValueError:
1104 except ValueError:
1105 raise Abort(_("invalid day spec: %s") % date[1:])
1105 raise Abort(_("invalid day spec: %s") % date[1:])
1106 if days < 0:
1106 if days < 0:
1107 raise Abort(_("%s must be nonnegative (see 'hg help dates')")
1107 raise Abort(_("%s must be nonnegative (see 'hg help dates')")
1108 % date[1:])
1108 % date[1:])
1109 when = makedate()[0] - days * 3600 * 24
1109 when = makedate()[0] - days * 3600 * 24
1110 return lambda x: x >= when
1110 return lambda x: x >= when
1111 elif " to " in date:
1111 elif " to " in date:
1112 a, b = date.split(" to ")
1112 a, b = date.split(" to ")
1113 start, stop = lower(a), upper(b)
1113 start, stop = lower(a), upper(b)
1114 return lambda x: x >= start and x <= stop
1114 return lambda x: x >= start and x <= stop
1115 else:
1115 else:
1116 start, stop = lower(date), upper(date)
1116 start, stop = lower(date), upper(date)
1117 return lambda x: x >= start and x <= stop
1117 return lambda x: x >= start and x <= stop
1118
1118
1119 def shortuser(user):
1119 def shortuser(user):
1120 """Return a short representation of a user name or email address."""
1120 """Return a short representation of a user name or email address."""
1121 f = user.find('@')
1121 f = user.find('@')
1122 if f >= 0:
1122 if f >= 0:
1123 user = user[:f]
1123 user = user[:f]
1124 f = user.find('<')
1124 f = user.find('<')
1125 if f >= 0:
1125 if f >= 0:
1126 user = user[f + 1:]
1126 user = user[f + 1:]
1127 f = user.find(' ')
1127 f = user.find(' ')
1128 if f >= 0:
1128 if f >= 0:
1129 user = user[:f]
1129 user = user[:f]
1130 f = user.find('.')
1130 f = user.find('.')
1131 if f >= 0:
1131 if f >= 0:
1132 user = user[:f]
1132 user = user[:f]
1133 return user
1133 return user
1134
1134
1135 def emailuser(user):
1135 def emailuser(user):
1136 """Return the user portion of an email address."""
1136 """Return the user portion of an email address."""
1137 f = user.find('@')
1137 f = user.find('@')
1138 if f >= 0:
1138 if f >= 0:
1139 user = user[:f]
1139 user = user[:f]
1140 f = user.find('<')
1140 f = user.find('<')
1141 if f >= 0:
1141 if f >= 0:
1142 user = user[f + 1:]
1142 user = user[f + 1:]
1143 return user
1143 return user
1144
1144
1145 def email(author):
1145 def email(author):
1146 '''get email of author.'''
1146 '''get email of author.'''
1147 r = author.find('>')
1147 r = author.find('>')
1148 if r == -1:
1148 if r == -1:
1149 r = None
1149 r = None
1150 return author[author.find('<') + 1:r]
1150 return author[author.find('<') + 1:r]
1151
1151
1152 def _ellipsis(text, maxlength):
1152 def _ellipsis(text, maxlength):
1153 if len(text) <= maxlength:
1153 if len(text) <= maxlength:
1154 return text, False
1154 return text, False
1155 else:
1155 else:
1156 return "%s..." % (text[:maxlength - 3]), True
1156 return "%s..." % (text[:maxlength - 3]), True
1157
1157
1158 def ellipsis(text, maxlength=400):
1158 def ellipsis(text, maxlength=400):
1159 """Trim string to at most maxlength (default: 400) characters."""
1159 """Trim string to at most maxlength (default: 400) characters."""
1160 try:
1160 try:
1161 # use unicode not to split at intermediate multi-byte sequence
1161 # use unicode not to split at intermediate multi-byte sequence
1162 utext, truncated = _ellipsis(text.decode(encoding.encoding),
1162 utext, truncated = _ellipsis(text.decode(encoding.encoding),
1163 maxlength)
1163 maxlength)
1164 if not truncated:
1164 if not truncated:
1165 return text
1165 return text
1166 return utext.encode(encoding.encoding)
1166 return utext.encode(encoding.encoding)
1167 except (UnicodeDecodeError, UnicodeEncodeError):
1167 except (UnicodeDecodeError, UnicodeEncodeError):
1168 return _ellipsis(text, maxlength)[0]
1168 return _ellipsis(text, maxlength)[0]
1169
1169
1170 _byteunits = (
1170 _byteunits = (
1171 (100, 1 << 30, _('%.0f GB')),
1171 (100, 1 << 30, _('%.0f GB')),
1172 (10, 1 << 30, _('%.1f GB')),
1172 (10, 1 << 30, _('%.1f GB')),
1173 (1, 1 << 30, _('%.2f GB')),
1173 (1, 1 << 30, _('%.2f GB')),
1174 (100, 1 << 20, _('%.0f MB')),
1174 (100, 1 << 20, _('%.0f MB')),
1175 (10, 1 << 20, _('%.1f MB')),
1175 (10, 1 << 20, _('%.1f MB')),
1176 (1, 1 << 20, _('%.2f MB')),
1176 (1, 1 << 20, _('%.2f MB')),
1177 (100, 1 << 10, _('%.0f KB')),
1177 (100, 1 << 10, _('%.0f KB')),
1178 (10, 1 << 10, _('%.1f KB')),
1178 (10, 1 << 10, _('%.1f KB')),
1179 (1, 1 << 10, _('%.2f KB')),
1179 (1, 1 << 10, _('%.2f KB')),
1180 (1, 1, _('%.0f bytes')),
1180 (1, 1, _('%.0f bytes')),
1181 )
1181 )
1182
1182
1183 def bytecount(nbytes):
1183 def bytecount(nbytes):
1184 '''return byte count formatted as readable string, with units'''
1184 '''return byte count formatted as readable string, with units'''
1185
1185
1186 for multiplier, divisor, format in _byteunits:
1186 for multiplier, divisor, format in _byteunits:
1187 if nbytes >= divisor * multiplier:
1187 if nbytes >= divisor * multiplier:
1188 return format % (nbytes / float(divisor))
1188 return format % (nbytes / float(divisor))
1189 return units[-1][2] % nbytes
1189 return units[-1][2] % nbytes
1190
1190
1191 def uirepr(s):
1191 def uirepr(s):
1192 # Avoid double backslash in Windows path repr()
1192 # Avoid double backslash in Windows path repr()
1193 return repr(s).replace('\\\\', '\\')
1193 return repr(s).replace('\\\\', '\\')
1194
1194
1195 # delay import of textwrap
1195 # delay import of textwrap
1196 def MBTextWrapper(**kwargs):
1196 def MBTextWrapper(**kwargs):
1197 class tw(textwrap.TextWrapper):
1197 class tw(textwrap.TextWrapper):
1198 """
1198 """
1199 Extend TextWrapper for width-awareness.
1199 Extend TextWrapper for width-awareness.
1200
1200
1201 Neither number of 'bytes' in any encoding nor 'characters' is
1201 Neither number of 'bytes' in any encoding nor 'characters' is
1202 appropriate to calculate terminal columns for specified string.
1202 appropriate to calculate terminal columns for specified string.
1203
1203
1204 Original TextWrapper implementation uses built-in 'len()' directly,
1204 Original TextWrapper implementation uses built-in 'len()' directly,
1205 so overriding is needed to use width information of each characters.
1205 so overriding is needed to use width information of each characters.
1206
1206
1207 In addition, characters classified into 'ambiguous' width are
1207 In addition, characters classified into 'ambiguous' width are
1208 treated as wide in east asian area, but as narrow in other.
1208 treated as wide in east asian area, but as narrow in other.
1209
1209
1210 This requires use decision to determine width of such characters.
1210 This requires use decision to determine width of such characters.
1211 """
1211 """
1212 def __init__(self, **kwargs):
1212 def __init__(self, **kwargs):
1213 textwrap.TextWrapper.__init__(self, **kwargs)
1213 textwrap.TextWrapper.__init__(self, **kwargs)
1214
1214
1215 # for compatibility between 2.4 and 2.6
1215 # for compatibility between 2.4 and 2.6
1216 if getattr(self, 'drop_whitespace', None) is None:
1216 if getattr(self, 'drop_whitespace', None) is None:
1217 self.drop_whitespace = kwargs.get('drop_whitespace', True)
1217 self.drop_whitespace = kwargs.get('drop_whitespace', True)
1218
1218
1219 def _cutdown(self, ucstr, space_left):
1219 def _cutdown(self, ucstr, space_left):
1220 l = 0
1220 l = 0
1221 colwidth = encoding.ucolwidth
1221 colwidth = encoding.ucolwidth
1222 for i in xrange(len(ucstr)):
1222 for i in xrange(len(ucstr)):
1223 l += colwidth(ucstr[i])
1223 l += colwidth(ucstr[i])
1224 if space_left < l:
1224 if space_left < l:
1225 return (ucstr[:i], ucstr[i:])
1225 return (ucstr[:i], ucstr[i:])
1226 return ucstr, ''
1226 return ucstr, ''
1227
1227
1228 # overriding of base class
1228 # overriding of base class
1229 def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width):
1229 def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width):
1230 space_left = max(width - cur_len, 1)
1230 space_left = max(width - cur_len, 1)
1231
1231
1232 if self.break_long_words:
1232 if self.break_long_words:
1233 cut, res = self._cutdown(reversed_chunks[-1], space_left)
1233 cut, res = self._cutdown(reversed_chunks[-1], space_left)
1234 cur_line.append(cut)
1234 cur_line.append(cut)
1235 reversed_chunks[-1] = res
1235 reversed_chunks[-1] = res
1236 elif not cur_line:
1236 elif not cur_line:
1237 cur_line.append(reversed_chunks.pop())
1237 cur_line.append(reversed_chunks.pop())
1238
1238
1239 # this overriding code is imported from TextWrapper of python 2.6
1239 # this overriding code is imported from TextWrapper of python 2.6
1240 # to calculate columns of string by 'encoding.ucolwidth()'
1240 # to calculate columns of string by 'encoding.ucolwidth()'
1241 def _wrap_chunks(self, chunks):
1241 def _wrap_chunks(self, chunks):
1242 colwidth = encoding.ucolwidth
1242 colwidth = encoding.ucolwidth
1243
1243
1244 lines = []
1244 lines = []
1245 if self.width <= 0:
1245 if self.width <= 0:
1246 raise ValueError("invalid width %r (must be > 0)" % self.width)
1246 raise ValueError("invalid width %r (must be > 0)" % self.width)
1247
1247
1248 # Arrange in reverse order so items can be efficiently popped
1248 # Arrange in reverse order so items can be efficiently popped
1249 # from a stack of chucks.
1249 # from a stack of chucks.
1250 chunks.reverse()
1250 chunks.reverse()
1251
1251
1252 while chunks:
1252 while chunks:
1253
1253
1254 # Start the list of chunks that will make up the current line.
1254 # Start the list of chunks that will make up the current line.
1255 # cur_len is just the length of all the chunks in cur_line.
1255 # cur_len is just the length of all the chunks in cur_line.
1256 cur_line = []
1256 cur_line = []
1257 cur_len = 0
1257 cur_len = 0
1258
1258
1259 # Figure out which static string will prefix this line.
1259 # Figure out which static string will prefix this line.
1260 if lines:
1260 if lines:
1261 indent = self.subsequent_indent
1261 indent = self.subsequent_indent
1262 else:
1262 else:
1263 indent = self.initial_indent
1263 indent = self.initial_indent
1264
1264
1265 # Maximum width for this line.
1265 # Maximum width for this line.
1266 width = self.width - len(indent)
1266 width = self.width - len(indent)
1267
1267
1268 # First chunk on line is whitespace -- drop it, unless this
1268 # First chunk on line is whitespace -- drop it, unless this
1269 # is the very beginning of the text (ie. no lines started yet).
1269 # is the very beginning of the text (ie. no lines started yet).
1270 if self.drop_whitespace and chunks[-1].strip() == '' and lines:
1270 if self.drop_whitespace and chunks[-1].strip() == '' and lines:
1271 del chunks[-1]
1271 del chunks[-1]
1272
1272
1273 while chunks:
1273 while chunks:
1274 l = colwidth(chunks[-1])
1274 l = colwidth(chunks[-1])
1275
1275
1276 # Can at least squeeze this chunk onto the current line.
1276 # Can at least squeeze this chunk onto the current line.
1277 if cur_len + l <= width:
1277 if cur_len + l <= width:
1278 cur_line.append(chunks.pop())
1278 cur_line.append(chunks.pop())
1279 cur_len += l
1279 cur_len += l
1280
1280
1281 # Nope, this line is full.
1281 # Nope, this line is full.
1282 else:
1282 else:
1283 break
1283 break
1284
1284
1285 # The current line is full, and the next chunk is too big to
1285 # The current line is full, and the next chunk is too big to
1286 # fit on *any* line (not just this one).
1286 # fit on *any* line (not just this one).
1287 if chunks and colwidth(chunks[-1]) > width:
1287 if chunks and colwidth(chunks[-1]) > width:
1288 self._handle_long_word(chunks, cur_line, cur_len, width)
1288 self._handle_long_word(chunks, cur_line, cur_len, width)
1289
1289
1290 # If the last chunk on this line is all whitespace, drop it.
1290 # If the last chunk on this line is all whitespace, drop it.
1291 if (self.drop_whitespace and
1291 if (self.drop_whitespace and
1292 cur_line and cur_line[-1].strip() == ''):
1292 cur_line and cur_line[-1].strip() == ''):
1293 del cur_line[-1]
1293 del cur_line[-1]
1294
1294
1295 # Convert current line back to a string and store it in list
1295 # Convert current line back to a string and store it in list
1296 # of all lines (return value).
1296 # of all lines (return value).
1297 if cur_line:
1297 if cur_line:
1298 lines.append(indent + ''.join(cur_line))
1298 lines.append(indent + ''.join(cur_line))
1299
1299
1300 return lines
1300 return lines
1301
1301
1302 global MBTextWrapper
1302 global MBTextWrapper
1303 MBTextWrapper = tw
1303 MBTextWrapper = tw
1304 return tw(**kwargs)
1304 return tw(**kwargs)
1305
1305
1306 def wrap(line, width, initindent='', hangindent=''):
1306 def wrap(line, width, initindent='', hangindent=''):
1307 maxindent = max(len(hangindent), len(initindent))
1307 maxindent = max(len(hangindent), len(initindent))
1308 if width <= maxindent:
1308 if width <= maxindent:
1309 # adjust for weird terminal size
1309 # adjust for weird terminal size
1310 width = max(78, maxindent + 1)
1310 width = max(78, maxindent + 1)
1311 line = line.decode(encoding.encoding, encoding.encodingmode)
1311 line = line.decode(encoding.encoding, encoding.encodingmode)
1312 initindent = initindent.decode(encoding.encoding, encoding.encodingmode)
1312 initindent = initindent.decode(encoding.encoding, encoding.encodingmode)
1313 hangindent = hangindent.decode(encoding.encoding, encoding.encodingmode)
1313 hangindent = hangindent.decode(encoding.encoding, encoding.encodingmode)
1314 wrapper = MBTextWrapper(width=width,
1314 wrapper = MBTextWrapper(width=width,
1315 initial_indent=initindent,
1315 initial_indent=initindent,
1316 subsequent_indent=hangindent)
1316 subsequent_indent=hangindent)
1317 return wrapper.fill(line).encode(encoding.encoding)
1317 return wrapper.fill(line).encode(encoding.encoding)
1318
1318
1319 def iterlines(iterator):
1319 def iterlines(iterator):
1320 for chunk in iterator:
1320 for chunk in iterator:
1321 for line in chunk.splitlines():
1321 for line in chunk.splitlines():
1322 yield line
1322 yield line
1323
1323
1324 def expandpath(path):
1324 def expandpath(path):
1325 return os.path.expanduser(os.path.expandvars(path))
1325 return os.path.expanduser(os.path.expandvars(path))
1326
1326
1327 def hgcmd():
1327 def hgcmd():
1328 """Return the command used to execute current hg
1328 """Return the command used to execute current hg
1329
1329
1330 This is different from hgexecutable() because on Windows we want
1330 This is different from hgexecutable() because on Windows we want
1331 to avoid things opening new shell windows like batch files, so we
1331 to avoid things opening new shell windows like batch files, so we
1332 get either the python call or current executable.
1332 get either the python call or current executable.
1333 """
1333 """
1334 if mainfrozen():
1334 if mainfrozen():
1335 return [sys.executable]
1335 return [sys.executable]
1336 return gethgcmd()
1336 return gethgcmd()
1337
1337
1338 def rundetached(args, condfn):
1338 def rundetached(args, condfn):
1339 """Execute the argument list in a detached process.
1339 """Execute the argument list in a detached process.
1340
1340
1341 condfn is a callable which is called repeatedly and should return
1341 condfn is a callable which is called repeatedly and should return
1342 True once the child process is known to have started successfully.
1342 True once the child process is known to have started successfully.
1343 At this point, the child process PID is returned. If the child
1343 At this point, the child process PID is returned. If the child
1344 process fails to start or finishes before condfn() evaluates to
1344 process fails to start or finishes before condfn() evaluates to
1345 True, return -1.
1345 True, return -1.
1346 """
1346 """
1347 # Windows case is easier because the child process is either
1347 # Windows case is easier because the child process is either
1348 # successfully starting and validating the condition or exiting
1348 # successfully starting and validating the condition or exiting
1349 # on failure. We just poll on its PID. On Unix, if the child
1349 # on failure. We just poll on its PID. On Unix, if the child
1350 # process fails to start, it will be left in a zombie state until
1350 # process fails to start, it will be left in a zombie state until
1351 # the parent wait on it, which we cannot do since we expect a long
1351 # the parent wait on it, which we cannot do since we expect a long
1352 # running process on success. Instead we listen for SIGCHLD telling
1352 # running process on success. Instead we listen for SIGCHLD telling
1353 # us our child process terminated.
1353 # us our child process terminated.
1354 terminated = set()
1354 terminated = set()
1355 def handler(signum, frame):
1355 def handler(signum, frame):
1356 terminated.add(os.wait())
1356 terminated.add(os.wait())
1357 prevhandler = None
1357 prevhandler = None
1358 SIGCHLD = getattr(signal, 'SIGCHLD', None)
1358 SIGCHLD = getattr(signal, 'SIGCHLD', None)
1359 if SIGCHLD is not None:
1359 if SIGCHLD is not None:
1360 prevhandler = signal.signal(SIGCHLD, handler)
1360 prevhandler = signal.signal(SIGCHLD, handler)
1361 try:
1361 try:
1362 pid = spawndetached(args)
1362 pid = spawndetached(args)
1363 while not condfn():
1363 while not condfn():
1364 if ((pid in terminated or not testpid(pid))
1364 if ((pid in terminated or not testpid(pid))
1365 and not condfn()):
1365 and not condfn()):
1366 return -1
1366 return -1
1367 time.sleep(0.1)
1367 time.sleep(0.1)
1368 return pid
1368 return pid
1369 finally:
1369 finally:
1370 if prevhandler is not None:
1370 if prevhandler is not None:
1371 signal.signal(signal.SIGCHLD, prevhandler)
1371 signal.signal(signal.SIGCHLD, prevhandler)
1372
1372
1373 try:
1373 try:
1374 any, all = any, all
1374 any, all = any, all
1375 except NameError:
1375 except NameError:
1376 def any(iterable):
1376 def any(iterable):
1377 for i in iterable:
1377 for i in iterable:
1378 if i:
1378 if i:
1379 return True
1379 return True
1380 return False
1380 return False
1381
1381
1382 def all(iterable):
1382 def all(iterable):
1383 for i in iterable:
1383 for i in iterable:
1384 if not i:
1384 if not i:
1385 return False
1385 return False
1386 return True
1386 return True
1387
1387
1388 def interpolate(prefix, mapping, s, fn=None, escape_prefix=False):
1388 def interpolate(prefix, mapping, s, fn=None, escape_prefix=False):
1389 """Return the result of interpolating items in the mapping into string s.
1389 """Return the result of interpolating items in the mapping into string s.
1390
1390
1391 prefix is a single character string, or a two character string with
1391 prefix is a single character string, or a two character string with
1392 a backslash as the first character if the prefix needs to be escaped in
1392 a backslash as the first character if the prefix needs to be escaped in
1393 a regular expression.
1393 a regular expression.
1394
1394
1395 fn is an optional function that will be applied to the replacement text
1395 fn is an optional function that will be applied to the replacement text
1396 just before replacement.
1396 just before replacement.
1397
1397
1398 escape_prefix is an optional flag that allows using doubled prefix for
1398 escape_prefix is an optional flag that allows using doubled prefix for
1399 its escaping.
1399 its escaping.
1400 """
1400 """
1401 fn = fn or (lambda s: s)
1401 fn = fn or (lambda s: s)
1402 patterns = '|'.join(mapping.keys())
1402 patterns = '|'.join(mapping.keys())
1403 if escape_prefix:
1403 if escape_prefix:
1404 patterns += '|' + prefix
1404 patterns += '|' + prefix
1405 if len(prefix) > 1:
1405 if len(prefix) > 1:
1406 prefix_char = prefix[1:]
1406 prefix_char = prefix[1:]
1407 else:
1407 else:
1408 prefix_char = prefix
1408 prefix_char = prefix
1409 mapping[prefix_char] = prefix_char
1409 mapping[prefix_char] = prefix_char
1410 r = re.compile(r'%s(%s)' % (prefix, patterns))
1410 r = re.compile(r'%s(%s)' % (prefix, patterns))
1411 return r.sub(lambda x: fn(mapping[x.group()[1:]]), s)
1411 return r.sub(lambda x: fn(mapping[x.group()[1:]]), s)
1412
1412
1413 def getport(port):
1413 def getport(port):
1414 """Return the port for a given network service.
1414 """Return the port for a given network service.
1415
1415
1416 If port is an integer, it's returned as is. If it's a string, it's
1416 If port is an integer, it's returned as is. If it's a string, it's
1417 looked up using socket.getservbyname(). If there's no matching
1417 looked up using socket.getservbyname(). If there's no matching
1418 service, util.Abort is raised.
1418 service, util.Abort is raised.
1419 """
1419 """
1420 try:
1420 try:
1421 return int(port)
1421 return int(port)
1422 except ValueError:
1422 except ValueError:
1423 pass
1423 pass
1424
1424
1425 try:
1425 try:
1426 return socket.getservbyname(port)
1426 return socket.getservbyname(port)
1427 except socket.error:
1427 except socket.error:
1428 raise Abort(_("no port number associated with service '%s'") % port)
1428 raise Abort(_("no port number associated with service '%s'") % port)
1429
1429
1430 _booleans = {'1': True, 'yes': True, 'true': True, 'on': True, 'always': True,
1430 _booleans = {'1': True, 'yes': True, 'true': True, 'on': True, 'always': True,
1431 '0': False, 'no': False, 'false': False, 'off': False,
1431 '0': False, 'no': False, 'false': False, 'off': False,
1432 'never': False}
1432 'never': False}
1433
1433
1434 def parsebool(s):
1434 def parsebool(s):
1435 """Parse s into a boolean.
1435 """Parse s into a boolean.
1436
1436
1437 If s is not a valid boolean, returns None.
1437 If s is not a valid boolean, returns None.
1438 """
1438 """
1439 return _booleans.get(s.lower(), None)
1439 return _booleans.get(s.lower(), None)
1440
1440
1441 _hexdig = '0123456789ABCDEFabcdef'
1441 _hexdig = '0123456789ABCDEFabcdef'
1442 _hextochr = dict((a + b, chr(int(a + b, 16)))
1442 _hextochr = dict((a + b, chr(int(a + b, 16)))
1443 for a in _hexdig for b in _hexdig)
1443 for a in _hexdig for b in _hexdig)
1444
1444
1445 def _urlunquote(s):
1445 def _urlunquote(s):
1446 """unquote('abc%20def') -> 'abc def'."""
1446 """unquote('abc%20def') -> 'abc def'."""
1447 res = s.split('%')
1447 res = s.split('%')
1448 # fastpath
1448 # fastpath
1449 if len(res) == 1:
1449 if len(res) == 1:
1450 return s
1450 return s
1451 s = res[0]
1451 s = res[0]
1452 for item in res[1:]:
1452 for item in res[1:]:
1453 try:
1453 try:
1454 s += _hextochr[item[:2]] + item[2:]
1454 s += _hextochr[item[:2]] + item[2:]
1455 except KeyError:
1455 except KeyError:
1456 s += '%' + item
1456 s += '%' + item
1457 except UnicodeDecodeError:
1457 except UnicodeDecodeError:
1458 s += unichr(int(item[:2], 16)) + item[2:]
1458 s += unichr(int(item[:2], 16)) + item[2:]
1459 return s
1459 return s
1460
1460
1461 class url(object):
1461 class url(object):
1462 r"""Reliable URL parser.
1462 r"""Reliable URL parser.
1463
1463
1464 This parses URLs and provides attributes for the following
1464 This parses URLs and provides attributes for the following
1465 components:
1465 components:
1466
1466
1467 <scheme>://<user>:<passwd>@<host>:<port>/<path>?<query>#<fragment>
1467 <scheme>://<user>:<passwd>@<host>:<port>/<path>?<query>#<fragment>
1468
1468
1469 Missing components are set to None. The only exception is
1469 Missing components are set to None. The only exception is
1470 fragment, which is set to '' if present but empty.
1470 fragment, which is set to '' if present but empty.
1471
1471
1472 If parsefragment is False, fragment is included in query. If
1472 If parsefragment is False, fragment is included in query. If
1473 parsequery is False, query is included in path. If both are
1473 parsequery is False, query is included in path. If both are
1474 False, both fragment and query are included in path.
1474 False, both fragment and query are included in path.
1475
1475
1476 See http://www.ietf.org/rfc/rfc2396.txt for more information.
1476 See http://www.ietf.org/rfc/rfc2396.txt for more information.
1477
1477
1478 Note that for backward compatibility reasons, bundle URLs do not
1478 Note that for backward compatibility reasons, bundle URLs do not
1479 take host names. That means 'bundle://../' has a path of '../'.
1479 take host names. That means 'bundle://../' has a path of '../'.
1480
1480
1481 Examples:
1481 Examples:
1482
1482
1483 >>> url('http://www.ietf.org/rfc/rfc2396.txt')
1483 >>> url('http://www.ietf.org/rfc/rfc2396.txt')
1484 <url scheme: 'http', host: 'www.ietf.org', path: 'rfc/rfc2396.txt'>
1484 <url scheme: 'http', host: 'www.ietf.org', path: 'rfc/rfc2396.txt'>
1485 >>> url('ssh://[::1]:2200//home/joe/repo')
1485 >>> url('ssh://[::1]:2200//home/joe/repo')
1486 <url scheme: 'ssh', host: '[::1]', port: '2200', path: '/home/joe/repo'>
1486 <url scheme: 'ssh', host: '[::1]', port: '2200', path: '/home/joe/repo'>
1487 >>> url('file:///home/joe/repo')
1487 >>> url('file:///home/joe/repo')
1488 <url scheme: 'file', path: '/home/joe/repo'>
1488 <url scheme: 'file', path: '/home/joe/repo'>
1489 >>> url('file:///c:/temp/foo/')
1489 >>> url('file:///c:/temp/foo/')
1490 <url scheme: 'file', path: 'c:/temp/foo/'>
1490 <url scheme: 'file', path: 'c:/temp/foo/'>
1491 >>> url('bundle:foo')
1491 >>> url('bundle:foo')
1492 <url scheme: 'bundle', path: 'foo'>
1492 <url scheme: 'bundle', path: 'foo'>
1493 >>> url('bundle://../foo')
1493 >>> url('bundle://../foo')
1494 <url scheme: 'bundle', path: '../foo'>
1494 <url scheme: 'bundle', path: '../foo'>
1495 >>> url(r'c:\foo\bar')
1495 >>> url(r'c:\foo\bar')
1496 <url path: 'c:\\foo\\bar'>
1496 <url path: 'c:\\foo\\bar'>
1497 >>> url(r'\\blah\blah\blah')
1497 >>> url(r'\\blah\blah\blah')
1498 <url path: '\\\\blah\\blah\\blah'>
1498 <url path: '\\\\blah\\blah\\blah'>
1499 >>> url(r'\\blah\blah\blah#baz')
1499 >>> url(r'\\blah\blah\blah#baz')
1500 <url path: '\\\\blah\\blah\\blah', fragment: 'baz'>
1500 <url path: '\\\\blah\\blah\\blah', fragment: 'baz'>
1501
1501
1502 Authentication credentials:
1502 Authentication credentials:
1503
1503
1504 >>> url('ssh://joe:xyz@x/repo')
1504 >>> url('ssh://joe:xyz@x/repo')
1505 <url scheme: 'ssh', user: 'joe', passwd: 'xyz', host: 'x', path: 'repo'>
1505 <url scheme: 'ssh', user: 'joe', passwd: 'xyz', host: 'x', path: 'repo'>
1506 >>> url('ssh://joe@x/repo')
1506 >>> url('ssh://joe@x/repo')
1507 <url scheme: 'ssh', user: 'joe', host: 'x', path: 'repo'>
1507 <url scheme: 'ssh', user: 'joe', host: 'x', path: 'repo'>
1508
1508
1509 Query strings and fragments:
1509 Query strings and fragments:
1510
1510
1511 >>> url('http://host/a?b#c')
1511 >>> url('http://host/a?b#c')
1512 <url scheme: 'http', host: 'host', path: 'a', query: 'b', fragment: 'c'>
1512 <url scheme: 'http', host: 'host', path: 'a', query: 'b', fragment: 'c'>
1513 >>> url('http://host/a?b#c', parsequery=False, parsefragment=False)
1513 >>> url('http://host/a?b#c', parsequery=False, parsefragment=False)
1514 <url scheme: 'http', host: 'host', path: 'a?b#c'>
1514 <url scheme: 'http', host: 'host', path: 'a?b#c'>
1515 """
1515 """
1516
1516
1517 _safechars = "!~*'()+"
1517 _safechars = "!~*'()+"
1518 _safepchars = "/!~*'()+:"
1518 _safepchars = "/!~*'()+:"
1519 _matchscheme = re.compile(r'^[a-zA-Z0-9+.\-]+:').match
1519 _matchscheme = re.compile(r'^[a-zA-Z0-9+.\-]+:').match
1520
1520
1521 def __init__(self, path, parsequery=True, parsefragment=True):
1521 def __init__(self, path, parsequery=True, parsefragment=True):
1522 # We slowly chomp away at path until we have only the path left
1522 # We slowly chomp away at path until we have only the path left
1523 self.scheme = self.user = self.passwd = self.host = None
1523 self.scheme = self.user = self.passwd = self.host = None
1524 self.port = self.path = self.query = self.fragment = None
1524 self.port = self.path = self.query = self.fragment = None
1525 self._localpath = True
1525 self._localpath = True
1526 self._hostport = ''
1526 self._hostport = ''
1527 self._origpath = path
1527 self._origpath = path
1528
1528
1529 if parsefragment and '#' in path:
1529 if parsefragment and '#' in path:
1530 path, self.fragment = path.split('#', 1)
1530 path, self.fragment = path.split('#', 1)
1531 if not path:
1531 if not path:
1532 path = None
1532 path = None
1533
1533
1534 # special case for Windows drive letters and UNC paths
1534 # special case for Windows drive letters and UNC paths
1535 if hasdriveletter(path) or path.startswith(r'\\'):
1535 if hasdriveletter(path) or path.startswith(r'\\'):
1536 self.path = path
1536 self.path = path
1537 return
1537 return
1538
1538
1539 # For compatibility reasons, we can't handle bundle paths as
1539 # For compatibility reasons, we can't handle bundle paths as
1540 # normal URLS
1540 # normal URLS
1541 if path.startswith('bundle:'):
1541 if path.startswith('bundle:'):
1542 self.scheme = 'bundle'
1542 self.scheme = 'bundle'
1543 path = path[7:]
1543 path = path[7:]
1544 if path.startswith('//'):
1544 if path.startswith('//'):
1545 path = path[2:]
1545 path = path[2:]
1546 self.path = path
1546 self.path = path
1547 return
1547 return
1548
1548
1549 if self._matchscheme(path):
1549 if self._matchscheme(path):
1550 parts = path.split(':', 1)
1550 parts = path.split(':', 1)
1551 if parts[0]:
1551 if parts[0]:
1552 self.scheme, path = parts
1552 self.scheme, path = parts
1553 self._localpath = False
1553 self._localpath = False
1554
1554
1555 if not path:
1555 if not path:
1556 path = None
1556 path = None
1557 if self._localpath:
1557 if self._localpath:
1558 self.path = ''
1558 self.path = ''
1559 return
1559 return
1560 else:
1560 else:
1561 if self._localpath:
1561 if self._localpath:
1562 self.path = path
1562 self.path = path
1563 return
1563 return
1564
1564
1565 if parsequery and '?' in path:
1565 if parsequery and '?' in path:
1566 path, self.query = path.split('?', 1)
1566 path, self.query = path.split('?', 1)
1567 if not path:
1567 if not path:
1568 path = None
1568 path = None
1569 if not self.query:
1569 if not self.query:
1570 self.query = None
1570 self.query = None
1571
1571
1572 # // is required to specify a host/authority
1572 # // is required to specify a host/authority
1573 if path and path.startswith('//'):
1573 if path and path.startswith('//'):
1574 parts = path[2:].split('/', 1)
1574 parts = path[2:].split('/', 1)
1575 if len(parts) > 1:
1575 if len(parts) > 1:
1576 self.host, path = parts
1576 self.host, path = parts
1577 path = path
1577 path = path
1578 else:
1578 else:
1579 self.host = parts[0]
1579 self.host = parts[0]
1580 path = None
1580 path = None
1581 if not self.host:
1581 if not self.host:
1582 self.host = None
1582 self.host = None
1583 # path of file:///d is /d
1583 # path of file:///d is /d
1584 # path of file:///d:/ is d:/, not /d:/
1584 # path of file:///d:/ is d:/, not /d:/
1585 if path and not hasdriveletter(path):
1585 if path and not hasdriveletter(path):
1586 path = '/' + path
1586 path = '/' + path
1587
1587
1588 if self.host and '@' in self.host:
1588 if self.host and '@' in self.host:
1589 self.user, self.host = self.host.rsplit('@', 1)
1589 self.user, self.host = self.host.rsplit('@', 1)
1590 if ':' in self.user:
1590 if ':' in self.user:
1591 self.user, self.passwd = self.user.split(':', 1)
1591 self.user, self.passwd = self.user.split(':', 1)
1592 if not self.host:
1592 if not self.host:
1593 self.host = None
1593 self.host = None
1594
1594
1595 # Don't split on colons in IPv6 addresses without ports
1595 # Don't split on colons in IPv6 addresses without ports
1596 if (self.host and ':' in self.host and
1596 if (self.host and ':' in self.host and
1597 not (self.host.startswith('[') and self.host.endswith(']'))):
1597 not (self.host.startswith('[') and self.host.endswith(']'))):
1598 self._hostport = self.host
1598 self._hostport = self.host
1599 self.host, self.port = self.host.rsplit(':', 1)
1599 self.host, self.port = self.host.rsplit(':', 1)
1600 if not self.host:
1600 if not self.host:
1601 self.host = None
1601 self.host = None
1602
1602
1603 if (self.host and self.scheme == 'file' and
1603 if (self.host and self.scheme == 'file' and
1604 self.host not in ('localhost', '127.0.0.1', '[::1]')):
1604 self.host not in ('localhost', '127.0.0.1', '[::1]')):
1605 raise Abort(_('file:// URLs can only refer to localhost'))
1605 raise Abort(_('file:// URLs can only refer to localhost'))
1606
1606
1607 self.path = path
1607 self.path = path
1608
1608
1609 # leave the query string escaped
1609 # leave the query string escaped
1610 for a in ('user', 'passwd', 'host', 'port',
1610 for a in ('user', 'passwd', 'host', 'port',
1611 'path', 'fragment'):
1611 'path', 'fragment'):
1612 v = getattr(self, a)
1612 v = getattr(self, a)
1613 if v is not None:
1613 if v is not None:
1614 setattr(self, a, _urlunquote(v))
1614 setattr(self, a, _urlunquote(v))
1615
1615
1616 def __repr__(self):
1616 def __repr__(self):
1617 attrs = []
1617 attrs = []
1618 for a in ('scheme', 'user', 'passwd', 'host', 'port', 'path',
1618 for a in ('scheme', 'user', 'passwd', 'host', 'port', 'path',
1619 'query', 'fragment'):
1619 'query', 'fragment'):
1620 v = getattr(self, a)
1620 v = getattr(self, a)
1621 if v is not None:
1621 if v is not None:
1622 attrs.append('%s: %r' % (a, v))
1622 attrs.append('%s: %r' % (a, v))
1623 return '<url %s>' % ', '.join(attrs)
1623 return '<url %s>' % ', '.join(attrs)
1624
1624
1625 def __str__(self):
1625 def __str__(self):
1626 r"""Join the URL's components back into a URL string.
1626 r"""Join the URL's components back into a URL string.
1627
1627
1628 Examples:
1628 Examples:
1629
1629
1630 >>> str(url('http://user:pw@host:80/c:/bob?fo:oo#ba:ar'))
1630 >>> str(url('http://user:pw@host:80/c:/bob?fo:oo#ba:ar'))
1631 'http://user:pw@host:80/c:/bob?fo:oo#ba:ar'
1631 'http://user:pw@host:80/c:/bob?fo:oo#ba:ar'
1632 >>> str(url('http://user:pw@host:80/?foo=bar&baz=42'))
1632 >>> str(url('http://user:pw@host:80/?foo=bar&baz=42'))
1633 'http://user:pw@host:80/?foo=bar&baz=42'
1633 'http://user:pw@host:80/?foo=bar&baz=42'
1634 >>> str(url('http://user:pw@host:80/?foo=bar%3dbaz'))
1634 >>> str(url('http://user:pw@host:80/?foo=bar%3dbaz'))
1635 'http://user:pw@host:80/?foo=bar%3dbaz'
1635 'http://user:pw@host:80/?foo=bar%3dbaz'
1636 >>> str(url('ssh://user:pw@[::1]:2200//home/joe#'))
1636 >>> str(url('ssh://user:pw@[::1]:2200//home/joe#'))
1637 'ssh://user:pw@[::1]:2200//home/joe#'
1637 'ssh://user:pw@[::1]:2200//home/joe#'
1638 >>> str(url('http://localhost:80//'))
1638 >>> str(url('http://localhost:80//'))
1639 'http://localhost:80//'
1639 'http://localhost:80//'
1640 >>> str(url('http://localhost:80/'))
1640 >>> str(url('http://localhost:80/'))
1641 'http://localhost:80/'
1641 'http://localhost:80/'
1642 >>> str(url('http://localhost:80'))
1642 >>> str(url('http://localhost:80'))
1643 'http://localhost:80/'
1643 'http://localhost:80/'
1644 >>> str(url('bundle:foo'))
1644 >>> str(url('bundle:foo'))
1645 'bundle:foo'
1645 'bundle:foo'
1646 >>> str(url('bundle://../foo'))
1646 >>> str(url('bundle://../foo'))
1647 'bundle:../foo'
1647 'bundle:../foo'
1648 >>> str(url('path'))
1648 >>> str(url('path'))
1649 'path'
1649 'path'
1650 >>> str(url('file:///tmp/foo/bar'))
1650 >>> str(url('file:///tmp/foo/bar'))
1651 'file:///tmp/foo/bar'
1651 'file:///tmp/foo/bar'
1652 >>> str(url('file:///c:/tmp/foo/bar'))
1652 >>> str(url('file:///c:/tmp/foo/bar'))
1653 'file:///c:/tmp/foo/bar'
1653 'file:///c:/tmp/foo/bar'
1654 >>> print url(r'bundle:foo\bar')
1654 >>> print url(r'bundle:foo\bar')
1655 bundle:foo\bar
1655 bundle:foo\bar
1656 """
1656 """
1657 if self._localpath:
1657 if self._localpath:
1658 s = self.path
1658 s = self.path
1659 if self.scheme == 'bundle':
1659 if self.scheme == 'bundle':
1660 s = 'bundle:' + s
1660 s = 'bundle:' + s
1661 if self.fragment:
1661 if self.fragment:
1662 s += '#' + self.fragment
1662 s += '#' + self.fragment
1663 return s
1663 return s
1664
1664
1665 s = self.scheme + ':'
1665 s = self.scheme + ':'
1666 if self.user or self.passwd or self.host:
1666 if self.user or self.passwd or self.host:
1667 s += '//'
1667 s += '//'
1668 elif self.scheme and (not self.path or self.path.startswith('/')
1668 elif self.scheme and (not self.path or self.path.startswith('/')
1669 or hasdriveletter(self.path)):
1669 or hasdriveletter(self.path)):
1670 s += '//'
1670 s += '//'
1671 if hasdriveletter(self.path):
1671 if hasdriveletter(self.path):
1672 s += '/'
1672 s += '/'
1673 if self.user:
1673 if self.user:
1674 s += urllib.quote(self.user, safe=self._safechars)
1674 s += urllib.quote(self.user, safe=self._safechars)
1675 if self.passwd:
1675 if self.passwd:
1676 s += ':' + urllib.quote(self.passwd, safe=self._safechars)
1676 s += ':' + urllib.quote(self.passwd, safe=self._safechars)
1677 if self.user or self.passwd:
1677 if self.user or self.passwd:
1678 s += '@'
1678 s += '@'
1679 if self.host:
1679 if self.host:
1680 if not (self.host.startswith('[') and self.host.endswith(']')):
1680 if not (self.host.startswith('[') and self.host.endswith(']')):
1681 s += urllib.quote(self.host)
1681 s += urllib.quote(self.host)
1682 else:
1682 else:
1683 s += self.host
1683 s += self.host
1684 if self.port:
1684 if self.port:
1685 s += ':' + urllib.quote(self.port)
1685 s += ':' + urllib.quote(self.port)
1686 if self.host:
1686 if self.host:
1687 s += '/'
1687 s += '/'
1688 if self.path:
1688 if self.path:
1689 # TODO: similar to the query string, we should not unescape the
1689 # TODO: similar to the query string, we should not unescape the
1690 # path when we store it, the path might contain '%2f' = '/',
1690 # path when we store it, the path might contain '%2f' = '/',
1691 # which we should *not* escape.
1691 # which we should *not* escape.
1692 s += urllib.quote(self.path, safe=self._safepchars)
1692 s += urllib.quote(self.path, safe=self._safepchars)
1693 if self.query:
1693 if self.query:
1694 # we store the query in escaped form.
1694 # we store the query in escaped form.
1695 s += '?' + self.query
1695 s += '?' + self.query
1696 if self.fragment is not None:
1696 if self.fragment is not None:
1697 s += '#' + urllib.quote(self.fragment, safe=self._safepchars)
1697 s += '#' + urllib.quote(self.fragment, safe=self._safepchars)
1698 return s
1698 return s
1699
1699
1700 def authinfo(self):
1700 def authinfo(self):
1701 user, passwd = self.user, self.passwd
1701 user, passwd = self.user, self.passwd
1702 try:
1702 try:
1703 self.user, self.passwd = None, None
1703 self.user, self.passwd = None, None
1704 s = str(self)
1704 s = str(self)
1705 finally:
1705 finally:
1706 self.user, self.passwd = user, passwd
1706 self.user, self.passwd = user, passwd
1707 if not self.user:
1707 if not self.user:
1708 return (s, None)
1708 return (s, None)
1709 # authinfo[1] is passed to urllib2 password manager, and its
1709 # authinfo[1] is passed to urllib2 password manager, and its
1710 # URIs must not contain credentials. The host is passed in the
1710 # URIs must not contain credentials. The host is passed in the
1711 # URIs list because Python < 2.4.3 uses only that to search for
1711 # URIs list because Python < 2.4.3 uses only that to search for
1712 # a password.
1712 # a password.
1713 return (s, (None, (s, self.host),
1713 return (s, (None, (s, self.host),
1714 self.user, self.passwd or ''))
1714 self.user, self.passwd or ''))
1715
1715
1716 def isabs(self):
1716 def isabs(self):
1717 if self.scheme and self.scheme != 'file':
1717 if self.scheme and self.scheme != 'file':
1718 return True # remote URL
1718 return True # remote URL
1719 if hasdriveletter(self.path):
1719 if hasdriveletter(self.path):
1720 return True # absolute for our purposes - can't be joined()
1720 return True # absolute for our purposes - can't be joined()
1721 if self.path.startswith(r'\\'):
1721 if self.path.startswith(r'\\'):
1722 return True # Windows UNC path
1722 return True # Windows UNC path
1723 if self.path.startswith('/'):
1723 if self.path.startswith('/'):
1724 return True # POSIX-style
1724 return True # POSIX-style
1725 return False
1725 return False
1726
1726
1727 def localpath(self):
1727 def localpath(self):
1728 if self.scheme == 'file' or self.scheme == 'bundle':
1728 if self.scheme == 'file' or self.scheme == 'bundle':
1729 path = self.path or '/'
1729 path = self.path or '/'
1730 # For Windows, we need to promote hosts containing drive
1730 # For Windows, we need to promote hosts containing drive
1731 # letters to paths with drive letters.
1731 # letters to paths with drive letters.
1732 if hasdriveletter(self._hostport):
1732 if hasdriveletter(self._hostport):
1733 path = self._hostport + '/' + self.path
1733 path = self._hostport + '/' + self.path
1734 elif (self.host is not None and self.path
1734 elif (self.host is not None and self.path
1735 and not hasdriveletter(path)):
1735 and not hasdriveletter(path)):
1736 path = '/' + path
1736 path = '/' + path
1737 return path
1737 return path
1738 return self._origpath
1738 return self._origpath
1739
1739
1740 def hasscheme(path):
1740 def hasscheme(path):
1741 return bool(url(path).scheme)
1741 return bool(url(path).scheme)
1742
1742
1743 def hasdriveletter(path):
1743 def hasdriveletter(path):
1744 return path and path[1:2] == ':' and path[0:1].isalpha()
1744 return path and path[1:2] == ':' and path[0:1].isalpha()
1745
1745
1746 def urllocalpath(path):
1746 def urllocalpath(path):
1747 return url(path, parsequery=False, parsefragment=False).localpath()
1747 return url(path, parsequery=False, parsefragment=False).localpath()
1748
1748
1749 def hidepassword(u):
1749 def hidepassword(u):
1750 '''hide user credential in a url string'''
1750 '''hide user credential in a url string'''
1751 u = url(u)
1751 u = url(u)
1752 if u.passwd:
1752 if u.passwd:
1753 u.passwd = '***'
1753 u.passwd = '***'
1754 return str(u)
1754 return str(u)
1755
1755
1756 def removeauth(u):
1756 def removeauth(u):
1757 '''remove all authentication information from a url string'''
1757 '''remove all authentication information from a url string'''
1758 u = url(u)
1758 u = url(u)
1759 u.user = u.passwd = None
1759 u.user = u.passwd = None
1760 return str(u)
1760 return str(u)
1761
1761
1762 def isatty(fd):
1762 def isatty(fd):
1763 try:
1763 try:
1764 return fd.isatty()
1764 return fd.isatty()
1765 except AttributeError:
1765 except AttributeError:
1766 return False
1766 return False
@@ -1,147 +1,147
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2
2
3 __doc__ = """Tiny HTTP Proxy.
3 __doc__ = """Tiny HTTP Proxy.
4
4
5 This module implements GET, HEAD, POST, PUT and DELETE methods
5 This module implements GET, HEAD, POST, PUT and DELETE methods
6 on BaseHTTPServer, and behaves as an HTTP proxy. The CONNECT
6 on BaseHTTPServer, and behaves as an HTTP proxy. The CONNECT
7 method is also implemented experimentally, but has not been
7 method is also implemented experimentally, but has not been
8 tested yet.
8 tested yet.
9
9
10 Any help will be greatly appreciated. SUZUKI Hisao
10 Any help will be greatly appreciated. SUZUKI Hisao
11 """
11 """
12
12
13 __version__ = "0.2.1"
13 __version__ = "0.2.1"
14
14
15 import BaseHTTPServer, select, socket, SocketServer, urlparse, os
15 import BaseHTTPServer, select, socket, SocketServer, urlparse, os
16
16
17 class ProxyHandler (BaseHTTPServer.BaseHTTPRequestHandler):
17 class ProxyHandler (BaseHTTPServer.BaseHTTPRequestHandler):
18 __base = BaseHTTPServer.BaseHTTPRequestHandler
18 __base = BaseHTTPServer.BaseHTTPRequestHandler
19 __base_handle = __base.handle
19 __base_handle = __base.handle
20
20
21 server_version = "TinyHTTPProxy/" + __version__
21 server_version = "TinyHTTPProxy/" + __version__
22 rbufsize = 0 # self.rfile Be unbuffered
22 rbufsize = 0 # self.rfile Be unbuffered
23
23
24 def handle(self):
24 def handle(self):
25 (ip, port) = self.client_address
25 (ip, port) = self.client_address
26 allowed = getattr(self, 'allowed_clients', None)
26 allowed = getattr(self, 'allowed_clients', None)
27 if allowed is not None and ip not in allowed:
27 if allowed is not None and ip not in allowed:
28 self.raw_requestline = self.rfile.readline()
28 self.raw_requestline = self.rfile.readline()
29 if self.parse_request():
29 if self.parse_request():
30 self.send_error(403)
30 self.send_error(403)
31 else:
31 else:
32 self.__base_handle()
32 self.__base_handle()
33
33
34 def log_request(self, code='-', size='-'):
34 def log_request(self, code='-', size='-'):
35 xheaders = [h for h in self.headers.items() if h[0].startswith('x-')]
35 xheaders = [h for h in self.headers.items() if h[0].startswith('x-')]
36 self.log_message('"%s" %s %s%s',
36 self.log_message('"%s" %s %s%s',
37 self.requestline, str(code), str(size),
37 self.requestline, str(code), str(size),
38 ''.join([' %s:%s' % h for h in sorted(xheaders)]))
38 ''.join([' %s:%s' % h for h in sorted(xheaders)]))
39
39
40 def _connect_to(self, netloc, soc):
40 def _connect_to(self, netloc, soc):
41 i = netloc.find(':')
41 i = netloc.find(':')
42 if i >= 0:
42 if i >= 0:
43 host_port = netloc[:i], int(netloc[i + 1:])
43 host_port = netloc[:i], int(netloc[i + 1:])
44 else:
44 else:
45 host_port = netloc, 80
45 host_port = netloc, 80
46 print "\t" "connect to %s:%d" % host_port
46 print "\t" "connect to %s:%d" % host_port
47 try: soc.connect(host_port)
47 try: soc.connect(host_port)
48 except socket.error, arg:
48 except socket.error, arg:
49 try: msg = arg[1]
49 try: msg = arg[1]
50 except: msg = arg
50 except (IndexError, TypeError): msg = arg
51 self.send_error(404, msg)
51 self.send_error(404, msg)
52 return 0
52 return 0
53 return 1
53 return 1
54
54
55 def do_CONNECT(self):
55 def do_CONNECT(self):
56 soc = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
56 soc = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
57 try:
57 try:
58 if self._connect_to(self.path, soc):
58 if self._connect_to(self.path, soc):
59 self.log_request(200)
59 self.log_request(200)
60 self.wfile.write(self.protocol_version +
60 self.wfile.write(self.protocol_version +
61 " 200 Connection established\r\n")
61 " 200 Connection established\r\n")
62 self.wfile.write("Proxy-agent: %s\r\n" % self.version_string())
62 self.wfile.write("Proxy-agent: %s\r\n" % self.version_string())
63 self.wfile.write("\r\n")
63 self.wfile.write("\r\n")
64 self._read_write(soc, 300)
64 self._read_write(soc, 300)
65 finally:
65 finally:
66 print "\t" "bye"
66 print "\t" "bye"
67 soc.close()
67 soc.close()
68 self.connection.close()
68 self.connection.close()
69
69
70 def do_GET(self):
70 def do_GET(self):
71 (scm, netloc, path, params, query, fragment) = urlparse.urlparse(
71 (scm, netloc, path, params, query, fragment) = urlparse.urlparse(
72 self.path, 'http')
72 self.path, 'http')
73 if scm != 'http' or fragment or not netloc:
73 if scm != 'http' or fragment or not netloc:
74 self.send_error(400, "bad url %s" % self.path)
74 self.send_error(400, "bad url %s" % self.path)
75 return
75 return
76 soc = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
76 soc = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
77 try:
77 try:
78 if self._connect_to(netloc, soc):
78 if self._connect_to(netloc, soc):
79 self.log_request()
79 self.log_request()
80 soc.send("%s %s %s\r\n" % (
80 soc.send("%s %s %s\r\n" % (
81 self.command,
81 self.command,
82 urlparse.urlunparse(('', '', path, params, query, '')),
82 urlparse.urlunparse(('', '', path, params, query, '')),
83 self.request_version))
83 self.request_version))
84 self.headers['Connection'] = 'close'
84 self.headers['Connection'] = 'close'
85 del self.headers['Proxy-Connection']
85 del self.headers['Proxy-Connection']
86 for key_val in self.headers.items():
86 for key_val in self.headers.items():
87 soc.send("%s: %s\r\n" % key_val)
87 soc.send("%s: %s\r\n" % key_val)
88 soc.send("\r\n")
88 soc.send("\r\n")
89 self._read_write(soc)
89 self._read_write(soc)
90 finally:
90 finally:
91 print "\t" "bye"
91 print "\t" "bye"
92 soc.close()
92 soc.close()
93 self.connection.close()
93 self.connection.close()
94
94
95 def _read_write(self, soc, max_idling=20):
95 def _read_write(self, soc, max_idling=20):
96 iw = [self.connection, soc]
96 iw = [self.connection, soc]
97 ow = []
97 ow = []
98 count = 0
98 count = 0
99 while True:
99 while True:
100 count += 1
100 count += 1
101 (ins, _, exs) = select.select(iw, ow, iw, 3)
101 (ins, _, exs) = select.select(iw, ow, iw, 3)
102 if exs:
102 if exs:
103 break
103 break
104 if ins:
104 if ins:
105 for i in ins:
105 for i in ins:
106 if i is soc:
106 if i is soc:
107 out = self.connection
107 out = self.connection
108 else:
108 else:
109 out = soc
109 out = soc
110 data = i.recv(8192)
110 data = i.recv(8192)
111 if data:
111 if data:
112 out.send(data)
112 out.send(data)
113 count = 0
113 count = 0
114 else:
114 else:
115 print "\t" "idle", count
115 print "\t" "idle", count
116 if count == max_idling:
116 if count == max_idling:
117 break
117 break
118
118
119 do_HEAD = do_GET
119 do_HEAD = do_GET
120 do_POST = do_GET
120 do_POST = do_GET
121 do_PUT = do_GET
121 do_PUT = do_GET
122 do_DELETE = do_GET
122 do_DELETE = do_GET
123
123
124 class ThreadingHTTPServer (SocketServer.ThreadingMixIn,
124 class ThreadingHTTPServer (SocketServer.ThreadingMixIn,
125 BaseHTTPServer.HTTPServer):
125 BaseHTTPServer.HTTPServer):
126 def __init__(self, *args, **kwargs):
126 def __init__(self, *args, **kwargs):
127 BaseHTTPServer.HTTPServer.__init__(self, *args, **kwargs)
127 BaseHTTPServer.HTTPServer.__init__(self, *args, **kwargs)
128 a = open("proxy.pid", "w")
128 a = open("proxy.pid", "w")
129 a.write(str(os.getpid()) + "\n")
129 a.write(str(os.getpid()) + "\n")
130 a.close()
130 a.close()
131
131
132 if __name__ == '__main__':
132 if __name__ == '__main__':
133 from sys import argv
133 from sys import argv
134 if argv[1:] and argv[1] in ('-h', '--help'):
134 if argv[1:] and argv[1] in ('-h', '--help'):
135 print argv[0], "[port [allowed_client_name ...]]"
135 print argv[0], "[port [allowed_client_name ...]]"
136 else:
136 else:
137 if argv[2:]:
137 if argv[2:]:
138 allowed = []
138 allowed = []
139 for name in argv[2:]:
139 for name in argv[2:]:
140 client = socket.gethostbyname(name)
140 client = socket.gethostbyname(name)
141 allowed.append(client)
141 allowed.append(client)
142 print "Accept: %s (%s)" % (client, name)
142 print "Accept: %s (%s)" % (client, name)
143 ProxyHandler.allowed_clients = allowed
143 ProxyHandler.allowed_clients = allowed
144 del argv[2:]
144 del argv[2:]
145 else:
145 else:
146 print "Any clients will be served..."
146 print "Any clients will be served..."
147 BaseHTTPServer.test(ProxyHandler, ThreadingHTTPServer)
147 BaseHTTPServer.test(ProxyHandler, ThreadingHTTPServer)
General Comments 0
You need to be logged in to leave comments. Login now