##// END OF EJS Templates
ui: refactor option setting...
Matt Mackall -
r8136:6b5522cb default
parent child Browse files
Show More
@@ -1,424 +1,432
1 # dispatch.py - command dispatching for mercurial
1 # dispatch.py - command dispatching 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
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7
7
8 from i18n import _
8 from i18n import _
9 import os, sys, atexit, signal, pdb, socket, errno, shlex, time
9 import os, sys, atexit, signal, pdb, socket, errno, shlex, time
10 import util, commands, hg, fancyopts, extensions, hook, error
10 import util, commands, hg, fancyopts, extensions, hook, error
11 import cmdutil, encoding
11 import cmdutil, encoding
12 import ui as _ui
12 import ui as _ui
13
13
14 def run():
14 def run():
15 "run the command in sys.argv"
15 "run the command in sys.argv"
16 sys.exit(dispatch(sys.argv[1:]))
16 sys.exit(dispatch(sys.argv[1:]))
17
17
18 def dispatch(args):
18 def dispatch(args):
19 "run the command specified in args"
19 "run the command specified in args"
20 try:
20 try:
21 u = _ui.ui(traceback='--traceback' in args)
21 u = _ui.ui()
22 if '--traceback' in args:
23 u.setconfig('ui', 'traceback', 'on')
22 except util.Abort, inst:
24 except util.Abort, inst:
23 sys.stderr.write(_("abort: %s\n") % inst)
25 sys.stderr.write(_("abort: %s\n") % inst)
24 return -1
26 return -1
25 return _runcatch(u, args)
27 return _runcatch(u, args)
26
28
27 def _runcatch(ui, args):
29 def _runcatch(ui, args):
28 def catchterm(*args):
30 def catchterm(*args):
29 raise error.SignalInterrupt
31 raise error.SignalInterrupt
30
32
31 for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM':
33 for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM':
32 num = getattr(signal, name, None)
34 num = getattr(signal, name, None)
33 if num: signal.signal(num, catchterm)
35 if num: signal.signal(num, catchterm)
34
36
35 try:
37 try:
36 try:
38 try:
37 # enter the debugger before command execution
39 # enter the debugger before command execution
38 if '--debugger' in args:
40 if '--debugger' in args:
39 pdb.set_trace()
41 pdb.set_trace()
40 try:
42 try:
41 return _dispatch(ui, args)
43 return _dispatch(ui, args)
42 finally:
44 finally:
43 ui.flush()
45 ui.flush()
44 except:
46 except:
45 # enter the debugger when we hit an exception
47 # enter the debugger when we hit an exception
46 if '--debugger' in args:
48 if '--debugger' in args:
47 pdb.post_mortem(sys.exc_info()[2])
49 pdb.post_mortem(sys.exc_info()[2])
48 ui.print_exc()
50 ui.print_exc()
49 raise
51 raise
50
52
51 # Global exception handling, alphabetically
53 # Global exception handling, alphabetically
52 # Mercurial-specific first, followed by built-in and library exceptions
54 # Mercurial-specific first, followed by built-in and library exceptions
53 except error.AmbiguousCommand, inst:
55 except error.AmbiguousCommand, inst:
54 ui.warn(_("hg: command '%s' is ambiguous:\n %s\n") %
56 ui.warn(_("hg: command '%s' is ambiguous:\n %s\n") %
55 (inst.args[0], " ".join(inst.args[1])))
57 (inst.args[0], " ".join(inst.args[1])))
56 except error.LockHeld, inst:
58 except error.LockHeld, inst:
57 if inst.errno == errno.ETIMEDOUT:
59 if inst.errno == errno.ETIMEDOUT:
58 reason = _('timed out waiting for lock held by %s') % inst.locker
60 reason = _('timed out waiting for lock held by %s') % inst.locker
59 else:
61 else:
60 reason = _('lock held by %s') % inst.locker
62 reason = _('lock held by %s') % inst.locker
61 ui.warn(_("abort: %s: %s\n") % (inst.desc or inst.filename, reason))
63 ui.warn(_("abort: %s: %s\n") % (inst.desc or inst.filename, reason))
62 except error.LockUnavailable, inst:
64 except error.LockUnavailable, inst:
63 ui.warn(_("abort: could not lock %s: %s\n") %
65 ui.warn(_("abort: could not lock %s: %s\n") %
64 (inst.desc or inst.filename, inst.strerror))
66 (inst.desc or inst.filename, inst.strerror))
65 except error.ParseError, inst:
67 except error.ParseError, inst:
66 if inst.args[0]:
68 if inst.args[0]:
67 ui.warn(_("hg %s: %s\n") % (inst.args[0], inst.args[1]))
69 ui.warn(_("hg %s: %s\n") % (inst.args[0], inst.args[1]))
68 commands.help_(ui, inst.args[0])
70 commands.help_(ui, inst.args[0])
69 else:
71 else:
70 ui.warn(_("hg: %s\n") % inst.args[1])
72 ui.warn(_("hg: %s\n") % inst.args[1])
71 commands.help_(ui, 'shortlist')
73 commands.help_(ui, 'shortlist')
72 except error.RepoError, inst:
74 except error.RepoError, inst:
73 ui.warn(_("abort: %s!\n") % inst)
75 ui.warn(_("abort: %s!\n") % inst)
74 except error.ResponseError, inst:
76 except error.ResponseError, inst:
75 ui.warn(_("abort: %s") % inst.args[0])
77 ui.warn(_("abort: %s") % inst.args[0])
76 if not isinstance(inst.args[1], basestring):
78 if not isinstance(inst.args[1], basestring):
77 ui.warn(" %r\n" % (inst.args[1],))
79 ui.warn(" %r\n" % (inst.args[1],))
78 elif not inst.args[1]:
80 elif not inst.args[1]:
79 ui.warn(_(" empty string\n"))
81 ui.warn(_(" empty string\n"))
80 else:
82 else:
81 ui.warn("\n%r\n" % util.ellipsis(inst.args[1]))
83 ui.warn("\n%r\n" % util.ellipsis(inst.args[1]))
82 except error.RevlogError, inst:
84 except error.RevlogError, inst:
83 ui.warn(_("abort: %s!\n") % inst)
85 ui.warn(_("abort: %s!\n") % inst)
84 except error.SignalInterrupt:
86 except error.SignalInterrupt:
85 ui.warn(_("killed!\n"))
87 ui.warn(_("killed!\n"))
86 except error.UnknownCommand, inst:
88 except error.UnknownCommand, inst:
87 ui.warn(_("hg: unknown command '%s'\n") % inst.args[0])
89 ui.warn(_("hg: unknown command '%s'\n") % inst.args[0])
88 commands.help_(ui, 'shortlist')
90 commands.help_(ui, 'shortlist')
89 except util.Abort, inst:
91 except util.Abort, inst:
90 ui.warn(_("abort: %s\n") % inst)
92 ui.warn(_("abort: %s\n") % inst)
91 except ImportError, inst:
93 except ImportError, inst:
92 m = str(inst).split()[-1]
94 m = str(inst).split()[-1]
93 ui.warn(_("abort: could not import module %s!\n") % m)
95 ui.warn(_("abort: could not import module %s!\n") % m)
94 if m in "mpatch bdiff".split():
96 if m in "mpatch bdiff".split():
95 ui.warn(_("(did you forget to compile extensions?)\n"))
97 ui.warn(_("(did you forget to compile extensions?)\n"))
96 elif m in "zlib".split():
98 elif m in "zlib".split():
97 ui.warn(_("(is your Python install correct?)\n"))
99 ui.warn(_("(is your Python install correct?)\n"))
98 except IOError, inst:
100 except IOError, inst:
99 if hasattr(inst, "code"):
101 if hasattr(inst, "code"):
100 ui.warn(_("abort: %s\n") % inst)
102 ui.warn(_("abort: %s\n") % inst)
101 elif hasattr(inst, "reason"):
103 elif hasattr(inst, "reason"):
102 try: # usually it is in the form (errno, strerror)
104 try: # usually it is in the form (errno, strerror)
103 reason = inst.reason.args[1]
105 reason = inst.reason.args[1]
104 except: # it might be anything, for example a string
106 except: # it might be anything, for example a string
105 reason = inst.reason
107 reason = inst.reason
106 ui.warn(_("abort: error: %s\n") % reason)
108 ui.warn(_("abort: error: %s\n") % reason)
107 elif hasattr(inst, "args") and inst.args[0] == errno.EPIPE:
109 elif hasattr(inst, "args") and inst.args[0] == errno.EPIPE:
108 if ui.debugflag:
110 if ui.debugflag:
109 ui.warn(_("broken pipe\n"))
111 ui.warn(_("broken pipe\n"))
110 elif getattr(inst, "strerror", None):
112 elif getattr(inst, "strerror", None):
111 if getattr(inst, "filename", None):
113 if getattr(inst, "filename", None):
112 ui.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
114 ui.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
113 else:
115 else:
114 ui.warn(_("abort: %s\n") % inst.strerror)
116 ui.warn(_("abort: %s\n") % inst.strerror)
115 else:
117 else:
116 raise
118 raise
117 except OSError, inst:
119 except OSError, inst:
118 if getattr(inst, "filename", None):
120 if getattr(inst, "filename", None):
119 ui.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
121 ui.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
120 else:
122 else:
121 ui.warn(_("abort: %s\n") % inst.strerror)
123 ui.warn(_("abort: %s\n") % inst.strerror)
122 except KeyboardInterrupt:
124 except KeyboardInterrupt:
123 try:
125 try:
124 ui.warn(_("interrupted!\n"))
126 ui.warn(_("interrupted!\n"))
125 except IOError, inst:
127 except IOError, inst:
126 if inst.errno == errno.EPIPE:
128 if inst.errno == errno.EPIPE:
127 if ui.debugflag:
129 if ui.debugflag:
128 ui.warn(_("\nbroken pipe\n"))
130 ui.warn(_("\nbroken pipe\n"))
129 else:
131 else:
130 raise
132 raise
131 except MemoryError:
133 except MemoryError:
132 ui.warn(_("abort: out of memory\n"))
134 ui.warn(_("abort: out of memory\n"))
133 except SystemExit, inst:
135 except SystemExit, inst:
134 # Commands shouldn't sys.exit directly, but give a return code.
136 # Commands shouldn't sys.exit directly, but give a return code.
135 # Just in case catch this and and pass exit code to caller.
137 # Just in case catch this and and pass exit code to caller.
136 return inst.code
138 return inst.code
137 except socket.error, inst:
139 except socket.error, inst:
138 ui.warn(_("abort: %s\n") % inst.args[-1])
140 ui.warn(_("abort: %s\n") % inst.args[-1])
139 except:
141 except:
140 ui.warn(_("** unknown exception encountered, details follow\n"))
142 ui.warn(_("** unknown exception encountered, details follow\n"))
141 ui.warn(_("** report bug details to "
143 ui.warn(_("** report bug details to "
142 "http://www.selenic.com/mercurial/bts\n"))
144 "http://www.selenic.com/mercurial/bts\n"))
143 ui.warn(_("** or mercurial@selenic.com\n"))
145 ui.warn(_("** or mercurial@selenic.com\n"))
144 ui.warn(_("** Mercurial Distributed SCM (version %s)\n")
146 ui.warn(_("** Mercurial Distributed SCM (version %s)\n")
145 % util.version())
147 % util.version())
146 ui.warn(_("** Extensions loaded: %s\n")
148 ui.warn(_("** Extensions loaded: %s\n")
147 % ", ".join([x[0] for x in extensions.extensions()]))
149 % ", ".join([x[0] for x in extensions.extensions()]))
148 raise
150 raise
149
151
150 return -1
152 return -1
151
153
152 def _findrepo(p):
154 def _findrepo(p):
153 while not os.path.isdir(os.path.join(p, ".hg")):
155 while not os.path.isdir(os.path.join(p, ".hg")):
154 oldp, p = p, os.path.dirname(p)
156 oldp, p = p, os.path.dirname(p)
155 if p == oldp:
157 if p == oldp:
156 return None
158 return None
157
159
158 return p
160 return p
159
161
160 def _parse(ui, args):
162 def _parse(ui, args):
161 options = {}
163 options = {}
162 cmdoptions = {}
164 cmdoptions = {}
163
165
164 try:
166 try:
165 args = fancyopts.fancyopts(args, commands.globalopts, options)
167 args = fancyopts.fancyopts(args, commands.globalopts, options)
166 except fancyopts.getopt.GetoptError, inst:
168 except fancyopts.getopt.GetoptError, inst:
167 raise error.ParseError(None, inst)
169 raise error.ParseError(None, inst)
168
170
169 if args:
171 if args:
170 cmd, args = args[0], args[1:]
172 cmd, args = args[0], args[1:]
171 aliases, i = cmdutil.findcmd(cmd, commands.table,
173 aliases, i = cmdutil.findcmd(cmd, commands.table,
172 ui.config("ui", "strict"))
174 ui.config("ui", "strict"))
173 cmd = aliases[0]
175 cmd = aliases[0]
174 defaults = ui.config("defaults", cmd)
176 defaults = ui.config("defaults", cmd)
175 if defaults:
177 if defaults:
176 args = shlex.split(defaults) + args
178 args = shlex.split(defaults) + args
177 c = list(i[1])
179 c = list(i[1])
178 else:
180 else:
179 cmd = None
181 cmd = None
180 c = []
182 c = []
181
183
182 # combine global options into local
184 # combine global options into local
183 for o in commands.globalopts:
185 for o in commands.globalopts:
184 c.append((o[0], o[1], options[o[1]], o[3]))
186 c.append((o[0], o[1], options[o[1]], o[3]))
185
187
186 try:
188 try:
187 args = fancyopts.fancyopts(args, c, cmdoptions, True)
189 args = fancyopts.fancyopts(args, c, cmdoptions, True)
188 except fancyopts.getopt.GetoptError, inst:
190 except fancyopts.getopt.GetoptError, inst:
189 raise error.ParseError(cmd, inst)
191 raise error.ParseError(cmd, inst)
190
192
191 # separate global options back out
193 # separate global options back out
192 for o in commands.globalopts:
194 for o in commands.globalopts:
193 n = o[1]
195 n = o[1]
194 options[n] = cmdoptions[n]
196 options[n] = cmdoptions[n]
195 del cmdoptions[n]
197 del cmdoptions[n]
196
198
197 return (cmd, cmd and i[0] or None, args, options, cmdoptions)
199 return (cmd, cmd and i[0] or None, args, options, cmdoptions)
198
200
199 def _parseconfig(config):
201 def _parseconfig(config):
200 """parse the --config options from the command line"""
202 """parse the --config options from the command line"""
201 parsed = []
203 parsed = []
202 for cfg in config:
204 for cfg in config:
203 try:
205 try:
204 name, value = cfg.split('=', 1)
206 name, value = cfg.split('=', 1)
205 section, name = name.split('.', 1)
207 section, name = name.split('.', 1)
206 if not section or not name:
208 if not section or not name:
207 raise IndexError
209 raise IndexError
208 parsed.append((section, name, value))
210 parsed.append((section, name, value))
209 except (IndexError, ValueError):
211 except (IndexError, ValueError):
210 raise util.Abort(_('malformed --config option: %s') % cfg)
212 raise util.Abort(_('malformed --config option: %s') % cfg)
211 return parsed
213 return parsed
212
214
213 def _earlygetopt(aliases, args):
215 def _earlygetopt(aliases, args):
214 """Return list of values for an option (or aliases).
216 """Return list of values for an option (or aliases).
215
217
216 The values are listed in the order they appear in args.
218 The values are listed in the order they appear in args.
217 The options and values are removed from args.
219 The options and values are removed from args.
218 """
220 """
219 try:
221 try:
220 argcount = args.index("--")
222 argcount = args.index("--")
221 except ValueError:
223 except ValueError:
222 argcount = len(args)
224 argcount = len(args)
223 shortopts = [opt for opt in aliases if len(opt) == 2]
225 shortopts = [opt for opt in aliases if len(opt) == 2]
224 values = []
226 values = []
225 pos = 0
227 pos = 0
226 while pos < argcount:
228 while pos < argcount:
227 if args[pos] in aliases:
229 if args[pos] in aliases:
228 if pos + 1 >= argcount:
230 if pos + 1 >= argcount:
229 # ignore and let getopt report an error if there is no value
231 # ignore and let getopt report an error if there is no value
230 break
232 break
231 del args[pos]
233 del args[pos]
232 values.append(args.pop(pos))
234 values.append(args.pop(pos))
233 argcount -= 2
235 argcount -= 2
234 elif args[pos][:2] in shortopts:
236 elif args[pos][:2] in shortopts:
235 # short option can have no following space, e.g. hg log -Rfoo
237 # short option can have no following space, e.g. hg log -Rfoo
236 values.append(args.pop(pos)[2:])
238 values.append(args.pop(pos)[2:])
237 argcount -= 1
239 argcount -= 1
238 else:
240 else:
239 pos += 1
241 pos += 1
240 return values
242 return values
241
243
242 def runcommand(lui, repo, cmd, fullargs, ui, options, d):
244 def runcommand(lui, repo, cmd, fullargs, ui, options, d):
243 # run pre-hook, and abort if it fails
245 # run pre-hook, and abort if it fails
244 ret = hook.hook(lui, repo, "pre-%s" % cmd, False, args=" ".join(fullargs))
246 ret = hook.hook(lui, repo, "pre-%s" % cmd, False, args=" ".join(fullargs))
245 if ret:
247 if ret:
246 return ret
248 return ret
247 ret = _runcommand(ui, options, cmd, d)
249 ret = _runcommand(ui, options, cmd, d)
248 # run post-hook, passing command result
250 # run post-hook, passing command result
249 hook.hook(lui, repo, "post-%s" % cmd, False, args=" ".join(fullargs),
251 hook.hook(lui, repo, "post-%s" % cmd, False, args=" ".join(fullargs),
250 result = ret)
252 result = ret)
251 return ret
253 return ret
252
254
253 _loaded = {}
255 _loaded = {}
254 def _dispatch(ui, args):
256 def _dispatch(ui, args):
255 # read --config before doing anything else
257 # read --config before doing anything else
256 # (e.g. to change trust settings for reading .hg/hgrc)
258 # (e.g. to change trust settings for reading .hg/hgrc)
257 config = _earlygetopt(['--config'], args)
259 config = _earlygetopt(['--config'], args)
258 if config:
260 if config:
259 ui.updateopts(config=_parseconfig(config))
261 ui.updateopts(_parseconfig(config))
260
262
261 # check for cwd
263 # check for cwd
262 cwd = _earlygetopt(['--cwd'], args)
264 cwd = _earlygetopt(['--cwd'], args)
263 if cwd:
265 if cwd:
264 os.chdir(cwd[-1])
266 os.chdir(cwd[-1])
265
267
266 # read the local repository .hgrc into a local ui object
268 # read the local repository .hgrc into a local ui object
267 path = _findrepo(os.getcwd()) or ""
269 path = _findrepo(os.getcwd()) or ""
268 if not path:
270 if not path:
269 lui = ui
271 lui = ui
270 if path:
272 if path:
271 try:
273 try:
272 lui = _ui.ui(parentui=ui)
274 lui = _ui.ui(parentui=ui)
273 lui.readconfig(os.path.join(path, ".hg", "hgrc"))
275 lui.readconfig(os.path.join(path, ".hg", "hgrc"))
274 except IOError:
276 except IOError:
275 pass
277 pass
276
278
277 # now we can expand paths, even ones in .hg/hgrc
279 # now we can expand paths, even ones in .hg/hgrc
278 rpath = _earlygetopt(["-R", "--repository", "--repo"], args)
280 rpath = _earlygetopt(["-R", "--repository", "--repo"], args)
279 if rpath:
281 if rpath:
280 path = lui.expandpath(rpath[-1])
282 path = lui.expandpath(rpath[-1])
281 lui = _ui.ui(parentui=ui)
283 lui = _ui.ui(parentui=ui)
282 lui.readconfig(os.path.join(path, ".hg", "hgrc"))
284 lui.readconfig(os.path.join(path, ".hg", "hgrc"))
283
285
284 extensions.loadall(lui)
286 extensions.loadall(lui)
285 for name, module in extensions.extensions():
287 for name, module in extensions.extensions():
286 if name in _loaded:
288 if name in _loaded:
287 continue
289 continue
288
290
289 # setup extensions
291 # setup extensions
290 # TODO this should be generalized to scheme, where extensions can
292 # TODO this should be generalized to scheme, where extensions can
291 # redepend on other extensions. then we should toposort them, and
293 # redepend on other extensions. then we should toposort them, and
292 # do initialization in correct order
294 # do initialization in correct order
293 extsetup = getattr(module, 'extsetup', None)
295 extsetup = getattr(module, 'extsetup', None)
294 if extsetup:
296 if extsetup:
295 extsetup()
297 extsetup()
296
298
297 cmdtable = getattr(module, 'cmdtable', {})
299 cmdtable = getattr(module, 'cmdtable', {})
298 overrides = [cmd for cmd in cmdtable if cmd in commands.table]
300 overrides = [cmd for cmd in cmdtable if cmd in commands.table]
299 if overrides:
301 if overrides:
300 ui.warn(_("extension '%s' overrides commands: %s\n")
302 ui.warn(_("extension '%s' overrides commands: %s\n")
301 % (name, " ".join(overrides)))
303 % (name, " ".join(overrides)))
302 commands.table.update(cmdtable)
304 commands.table.update(cmdtable)
303 _loaded[name] = 1
305 _loaded[name] = 1
304 # check for fallback encoding
306 # check for fallback encoding
305 fallback = lui.config('ui', 'fallbackencoding')
307 fallback = lui.config('ui', 'fallbackencoding')
306 if fallback:
308 if fallback:
307 encoding.fallbackencoding = fallback
309 encoding.fallbackencoding = fallback
308
310
309 fullargs = args
311 fullargs = args
310 cmd, func, args, options, cmdoptions = _parse(lui, args)
312 cmd, func, args, options, cmdoptions = _parse(lui, args)
311
313
312 if options["config"]:
314 if options["config"]:
313 raise util.Abort(_("Option --config may not be abbreviated!"))
315 raise util.Abort(_("Option --config may not be abbreviated!"))
314 if options["cwd"]:
316 if options["cwd"]:
315 raise util.Abort(_("Option --cwd may not be abbreviated!"))
317 raise util.Abort(_("Option --cwd may not be abbreviated!"))
316 if options["repository"]:
318 if options["repository"]:
317 raise util.Abort(_(
319 raise util.Abort(_(
318 "Option -R has to be separated from other options (i.e. not -qR) "
320 "Option -R has to be separated from other options (i.e. not -qR) "
319 "and --repository may only be abbreviated as --repo!"))
321 "and --repository may only be abbreviated as --repo!"))
320
322
321 if options["encoding"]:
323 if options["encoding"]:
322 encoding.encoding = options["encoding"]
324 encoding.encoding = options["encoding"]
323 if options["encodingmode"]:
325 if options["encodingmode"]:
324 encoding.encodingmode = options["encodingmode"]
326 encoding.encodingmode = options["encodingmode"]
325 if options["time"]:
327 if options["time"]:
326 def get_times():
328 def get_times():
327 t = os.times()
329 t = os.times()
328 if t[4] == 0.0: # Windows leaves this as zero, so use time.clock()
330 if t[4] == 0.0: # Windows leaves this as zero, so use time.clock()
329 t = (t[0], t[1], t[2], t[3], time.clock())
331 t = (t[0], t[1], t[2], t[3], time.clock())
330 return t
332 return t
331 s = get_times()
333 s = get_times()
332 def print_time():
334 def print_time():
333 t = get_times()
335 t = get_times()
334 ui.warn(_("Time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
336 ui.warn(_("Time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
335 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
337 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
336 atexit.register(print_time)
338 atexit.register(print_time)
337
339
338 ui.updateopts(options["verbose"], options["debug"], options["quiet"],
340 if options['verbose'] or options['debug'] or options['quiet']:
339 not options["noninteractive"], options["traceback"])
341 ui.setconfig('ui', 'verbose', str(bool(options['verbose'])))
342 ui.setconfig('ui', 'debug', str(bool(options['debug'])))
343 ui.setconfig('ui', 'quiet', str(bool(options['quiet'])))
344 if options['traceback']:
345 ui.setconfig('ui', 'traceback', 'on')
346 if options['noninteractive']:
347 ui.setconfig('ui', 'interactive', 'off')
340
348
341 if options['help']:
349 if options['help']:
342 return commands.help_(ui, cmd, options['version'])
350 return commands.help_(ui, cmd, options['version'])
343 elif options['version']:
351 elif options['version']:
344 return commands.version_(ui)
352 return commands.version_(ui)
345 elif not cmd:
353 elif not cmd:
346 return commands.help_(ui, 'shortlist')
354 return commands.help_(ui, 'shortlist')
347
355
348 repo = None
356 repo = None
349 if cmd not in commands.norepo.split():
357 if cmd not in commands.norepo.split():
350 try:
358 try:
351 repo = hg.repository(ui, path=path)
359 repo = hg.repository(ui, path=path)
352 ui = repo.ui
360 ui = repo.ui
353 if not repo.local():
361 if not repo.local():
354 raise util.Abort(_("repository '%s' is not local") % path)
362 raise util.Abort(_("repository '%s' is not local") % path)
355 ui.setconfig("bundle", "mainreporoot", repo.root)
363 ui.setconfig("bundle", "mainreporoot", repo.root)
356 except error.RepoError:
364 except error.RepoError:
357 if cmd not in commands.optionalrepo.split():
365 if cmd not in commands.optionalrepo.split():
358 if args and not path: # try to infer -R from command args
366 if args and not path: # try to infer -R from command args
359 repos = map(_findrepo, args)
367 repos = map(_findrepo, args)
360 guess = repos[0]
368 guess = repos[0]
361 if guess and repos.count(guess) == len(repos):
369 if guess and repos.count(guess) == len(repos):
362 return _dispatch(ui, ['--repository', guess] + fullargs)
370 return _dispatch(ui, ['--repository', guess] + fullargs)
363 if not path:
371 if not path:
364 raise error.RepoError(_("There is no Mercurial repository"
372 raise error.RepoError(_("There is no Mercurial repository"
365 " here (.hg not found)"))
373 " here (.hg not found)"))
366 raise
374 raise
367 args.insert(0, repo)
375 args.insert(0, repo)
368 elif rpath:
376 elif rpath:
369 ui.warn("warning: --repository ignored\n")
377 ui.warn("warning: --repository ignored\n")
370
378
371 d = lambda: util.checksignature(func)(ui, *args, **cmdoptions)
379 d = lambda: util.checksignature(func)(ui, *args, **cmdoptions)
372 return runcommand(lui, repo, cmd, fullargs, ui, options, d)
380 return runcommand(lui, repo, cmd, fullargs, ui, options, d)
373
381
374 def _runcommand(ui, options, cmd, cmdfunc):
382 def _runcommand(ui, options, cmd, cmdfunc):
375 def checkargs():
383 def checkargs():
376 try:
384 try:
377 return cmdfunc()
385 return cmdfunc()
378 except error.SignatureError:
386 except error.SignatureError:
379 raise error.ParseError(cmd, _("invalid arguments"))
387 raise error.ParseError(cmd, _("invalid arguments"))
380
388
381 if options['profile']:
389 if options['profile']:
382 format = ui.config('profiling', 'format', default='text')
390 format = ui.config('profiling', 'format', default='text')
383
391
384 if not format in ['text', 'kcachegrind']:
392 if not format in ['text', 'kcachegrind']:
385 ui.warn(_("unrecognized profiling format '%s'"
393 ui.warn(_("unrecognized profiling format '%s'"
386 " - Ignored\n") % format)
394 " - Ignored\n") % format)
387 format = 'text'
395 format = 'text'
388
396
389 output = ui.config('profiling', 'output')
397 output = ui.config('profiling', 'output')
390
398
391 if output:
399 if output:
392 path = os.path.expanduser(output)
400 path = os.path.expanduser(output)
393 path = ui.expandpath(path)
401 path = ui.expandpath(path)
394 ostream = open(path, 'wb')
402 ostream = open(path, 'wb')
395 else:
403 else:
396 ostream = sys.stderr
404 ostream = sys.stderr
397
405
398 try:
406 try:
399 from mercurial import lsprof
407 from mercurial import lsprof
400 except ImportError:
408 except ImportError:
401 raise util.Abort(_(
409 raise util.Abort(_(
402 'lsprof not available - install from '
410 'lsprof not available - install from '
403 'http://codespeak.net/svn/user/arigo/hack/misc/lsprof/'))
411 'http://codespeak.net/svn/user/arigo/hack/misc/lsprof/'))
404 p = lsprof.Profiler()
412 p = lsprof.Profiler()
405 p.enable(subcalls=True)
413 p.enable(subcalls=True)
406 try:
414 try:
407 return checkargs()
415 return checkargs()
408 finally:
416 finally:
409 p.disable()
417 p.disable()
410
418
411 if format == 'kcachegrind':
419 if format == 'kcachegrind':
412 import lsprofcalltree
420 import lsprofcalltree
413 calltree = lsprofcalltree.KCacheGrind(p)
421 calltree = lsprofcalltree.KCacheGrind(p)
414 calltree.output(ostream)
422 calltree.output(ostream)
415 else:
423 else:
416 # format == 'text'
424 # format == 'text'
417 stats = lsprof.Stats(p.getstats())
425 stats = lsprof.Stats(p.getstats())
418 stats.sort()
426 stats.sort()
419 stats.pprint(top=10, file=ostream, climit=5)
427 stats.pprint(top=10, file=ostream, climit=5)
420
428
421 if output:
429 if output:
422 ostream.close()
430 ostream.close()
423 else:
431 else:
424 return checkargs()
432 return checkargs()
@@ -1,310 +1,312
1 # hgweb/hgweb_mod.py - Web interface for a repository.
1 # hgweb/hgweb_mod.py - Web interface for a repository.
2 #
2 #
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 #
5 #
6 # This software may be used and distributed according to the terms
6 # This software may be used and distributed according to the terms
7 # of the GNU General Public License, incorporated herein by reference.
7 # of the GNU General Public License, incorporated herein by reference.
8
8
9 import os
9 import os
10 from mercurial import ui, hg, util, hook, error, encoding
10 from mercurial import ui, hg, util, hook, error, encoding
11 from mercurial import templater, templatefilters
11 from mercurial import templater, templatefilters
12 from common import get_mtime, ErrorResponse
12 from common import get_mtime, ErrorResponse
13 from common import HTTP_OK, HTTP_BAD_REQUEST, HTTP_NOT_FOUND, HTTP_SERVER_ERROR
13 from common import HTTP_OK, HTTP_BAD_REQUEST, HTTP_NOT_FOUND, HTTP_SERVER_ERROR
14 from common import HTTP_UNAUTHORIZED, HTTP_METHOD_NOT_ALLOWED
14 from common import HTTP_UNAUTHORIZED, HTTP_METHOD_NOT_ALLOWED
15 from request import wsgirequest
15 from request import wsgirequest
16 import webcommands, protocol, webutil
16 import webcommands, protocol, webutil
17
17
18 perms = {
18 perms = {
19 'changegroup': 'pull',
19 'changegroup': 'pull',
20 'changegroupsubset': 'pull',
20 'changegroupsubset': 'pull',
21 'unbundle': 'push',
21 'unbundle': 'push',
22 'stream_out': 'pull',
22 'stream_out': 'pull',
23 }
23 }
24
24
25 class hgweb(object):
25 class hgweb(object):
26 def __init__(self, repo, name=None):
26 def __init__(self, repo, name=None):
27 if isinstance(repo, str):
27 if isinstance(repo, str):
28 parentui = ui.ui(report_untrusted=False, interactive=False)
28 parentui = ui.ui()
29 parentui.setconfig('ui', 'report_untrusted', 'off')
30 parentui.setconfig('ui', 'interactive', 'off')
29 self.repo = hg.repository(parentui, repo)
31 self.repo = hg.repository(parentui, repo)
30 else:
32 else:
31 self.repo = repo
33 self.repo = repo
32
34
33 hook.redirect(True)
35 hook.redirect(True)
34 self.mtime = -1
36 self.mtime = -1
35 self.reponame = name
37 self.reponame = name
36 self.archives = 'zip', 'gz', 'bz2'
38 self.archives = 'zip', 'gz', 'bz2'
37 self.stripecount = 1
39 self.stripecount = 1
38 # a repo owner may set web.templates in .hg/hgrc to get any file
40 # a repo owner may set web.templates in .hg/hgrc to get any file
39 # readable by the user running the CGI script
41 # readable by the user running the CGI script
40 self.templatepath = self.config('web', 'templates')
42 self.templatepath = self.config('web', 'templates')
41
43
42 # The CGI scripts are often run by a user different from the repo owner.
44 # The CGI scripts are often run by a user different from the repo owner.
43 # Trust the settings from the .hg/hgrc files by default.
45 # Trust the settings from the .hg/hgrc files by default.
44 def config(self, section, name, default=None, untrusted=True):
46 def config(self, section, name, default=None, untrusted=True):
45 return self.repo.ui.config(section, name, default,
47 return self.repo.ui.config(section, name, default,
46 untrusted=untrusted)
48 untrusted=untrusted)
47
49
48 def configbool(self, section, name, default=False, untrusted=True):
50 def configbool(self, section, name, default=False, untrusted=True):
49 return self.repo.ui.configbool(section, name, default,
51 return self.repo.ui.configbool(section, name, default,
50 untrusted=untrusted)
52 untrusted=untrusted)
51
53
52 def configlist(self, section, name, default=None, untrusted=True):
54 def configlist(self, section, name, default=None, untrusted=True):
53 return self.repo.ui.configlist(section, name, default,
55 return self.repo.ui.configlist(section, name, default,
54 untrusted=untrusted)
56 untrusted=untrusted)
55
57
56 def refresh(self):
58 def refresh(self):
57 mtime = get_mtime(self.repo.root)
59 mtime = get_mtime(self.repo.root)
58 if mtime != self.mtime:
60 if mtime != self.mtime:
59 self.mtime = mtime
61 self.mtime = mtime
60 self.repo = hg.repository(self.repo.ui, self.repo.root)
62 self.repo = hg.repository(self.repo.ui, self.repo.root)
61 self.maxchanges = int(self.config("web", "maxchanges", 10))
63 self.maxchanges = int(self.config("web", "maxchanges", 10))
62 self.stripecount = int(self.config("web", "stripes", 1))
64 self.stripecount = int(self.config("web", "stripes", 1))
63 self.maxshortchanges = int(self.config("web", "maxshortchanges", 60))
65 self.maxshortchanges = int(self.config("web", "maxshortchanges", 60))
64 self.maxfiles = int(self.config("web", "maxfiles", 10))
66 self.maxfiles = int(self.config("web", "maxfiles", 10))
65 self.allowpull = self.configbool("web", "allowpull", True)
67 self.allowpull = self.configbool("web", "allowpull", True)
66 self.encoding = self.config("web", "encoding", encoding.encoding)
68 self.encoding = self.config("web", "encoding", encoding.encoding)
67
69
68 def run(self):
70 def run(self):
69 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
71 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
70 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
72 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
71 import mercurial.hgweb.wsgicgi as wsgicgi
73 import mercurial.hgweb.wsgicgi as wsgicgi
72 wsgicgi.launch(self)
74 wsgicgi.launch(self)
73
75
74 def __call__(self, env, respond):
76 def __call__(self, env, respond):
75 req = wsgirequest(env, respond)
77 req = wsgirequest(env, respond)
76 return self.run_wsgi(req)
78 return self.run_wsgi(req)
77
79
78 def run_wsgi(self, req):
80 def run_wsgi(self, req):
79
81
80 self.refresh()
82 self.refresh()
81
83
82 # process this if it's a protocol request
84 # process this if it's a protocol request
83 # protocol bits don't need to create any URLs
85 # protocol bits don't need to create any URLs
84 # and the clients always use the old URL structure
86 # and the clients always use the old URL structure
85
87
86 cmd = req.form.get('cmd', [''])[0]
88 cmd = req.form.get('cmd', [''])[0]
87 if cmd and cmd in protocol.__all__:
89 if cmd and cmd in protocol.__all__:
88 try:
90 try:
89 if cmd in perms:
91 if cmd in perms:
90 try:
92 try:
91 self.check_perm(req, perms[cmd])
93 self.check_perm(req, perms[cmd])
92 except ErrorResponse, inst:
94 except ErrorResponse, inst:
93 if cmd == 'unbundle':
95 if cmd == 'unbundle':
94 req.drain()
96 req.drain()
95 raise
97 raise
96 method = getattr(protocol, cmd)
98 method = getattr(protocol, cmd)
97 return method(self.repo, req)
99 return method(self.repo, req)
98 except ErrorResponse, inst:
100 except ErrorResponse, inst:
99 req.respond(inst, protocol.HGTYPE)
101 req.respond(inst, protocol.HGTYPE)
100 if not inst.message:
102 if not inst.message:
101 return []
103 return []
102 return '0\n%s\n' % inst.message,
104 return '0\n%s\n' % inst.message,
103
105
104 # work with CGI variables to create coherent structure
106 # work with CGI variables to create coherent structure
105 # use SCRIPT_NAME, PATH_INFO and QUERY_STRING as well as our REPO_NAME
107 # use SCRIPT_NAME, PATH_INFO and QUERY_STRING as well as our REPO_NAME
106
108
107 req.url = req.env['SCRIPT_NAME']
109 req.url = req.env['SCRIPT_NAME']
108 if not req.url.endswith('/'):
110 if not req.url.endswith('/'):
109 req.url += '/'
111 req.url += '/'
110 if 'REPO_NAME' in req.env:
112 if 'REPO_NAME' in req.env:
111 req.url += req.env['REPO_NAME'] + '/'
113 req.url += req.env['REPO_NAME'] + '/'
112
114
113 if 'PATH_INFO' in req.env:
115 if 'PATH_INFO' in req.env:
114 parts = req.env['PATH_INFO'].strip('/').split('/')
116 parts = req.env['PATH_INFO'].strip('/').split('/')
115 repo_parts = req.env.get('REPO_NAME', '').split('/')
117 repo_parts = req.env.get('REPO_NAME', '').split('/')
116 if parts[:len(repo_parts)] == repo_parts:
118 if parts[:len(repo_parts)] == repo_parts:
117 parts = parts[len(repo_parts):]
119 parts = parts[len(repo_parts):]
118 query = '/'.join(parts)
120 query = '/'.join(parts)
119 else:
121 else:
120 query = req.env['QUERY_STRING'].split('&', 1)[0]
122 query = req.env['QUERY_STRING'].split('&', 1)[0]
121 query = query.split(';', 1)[0]
123 query = query.split(';', 1)[0]
122
124
123 # translate user-visible url structure to internal structure
125 # translate user-visible url structure to internal structure
124
126
125 args = query.split('/', 2)
127 args = query.split('/', 2)
126 if 'cmd' not in req.form and args and args[0]:
128 if 'cmd' not in req.form and args and args[0]:
127
129
128 cmd = args.pop(0)
130 cmd = args.pop(0)
129 style = cmd.rfind('-')
131 style = cmd.rfind('-')
130 if style != -1:
132 if style != -1:
131 req.form['style'] = [cmd[:style]]
133 req.form['style'] = [cmd[:style]]
132 cmd = cmd[style+1:]
134 cmd = cmd[style+1:]
133
135
134 # avoid accepting e.g. style parameter as command
136 # avoid accepting e.g. style parameter as command
135 if hasattr(webcommands, cmd):
137 if hasattr(webcommands, cmd):
136 req.form['cmd'] = [cmd]
138 req.form['cmd'] = [cmd]
137 else:
139 else:
138 cmd = ''
140 cmd = ''
139
141
140 if cmd == 'static':
142 if cmd == 'static':
141 req.form['file'] = ['/'.join(args)]
143 req.form['file'] = ['/'.join(args)]
142 else:
144 else:
143 if args and args[0]:
145 if args and args[0]:
144 node = args.pop(0)
146 node = args.pop(0)
145 req.form['node'] = [node]
147 req.form['node'] = [node]
146 if args:
148 if args:
147 req.form['file'] = args
149 req.form['file'] = args
148
150
149 if cmd == 'archive':
151 if cmd == 'archive':
150 fn = req.form['node'][0]
152 fn = req.form['node'][0]
151 for type_, spec in self.archive_specs.iteritems():
153 for type_, spec in self.archive_specs.iteritems():
152 ext = spec[2]
154 ext = spec[2]
153 if fn.endswith(ext):
155 if fn.endswith(ext):
154 req.form['node'] = [fn[:-len(ext)]]
156 req.form['node'] = [fn[:-len(ext)]]
155 req.form['type'] = [type_]
157 req.form['type'] = [type_]
156
158
157 # process the web interface request
159 # process the web interface request
158
160
159 try:
161 try:
160 tmpl = self.templater(req)
162 tmpl = self.templater(req)
161 ctype = tmpl('mimetype', encoding=self.encoding)
163 ctype = tmpl('mimetype', encoding=self.encoding)
162 ctype = templater.stringify(ctype)
164 ctype = templater.stringify(ctype)
163
165
164 # check read permissions non-static content
166 # check read permissions non-static content
165 if cmd != 'static':
167 if cmd != 'static':
166 self.check_perm(req, None)
168 self.check_perm(req, None)
167
169
168 if cmd == '':
170 if cmd == '':
169 req.form['cmd'] = [tmpl.cache['default']]
171 req.form['cmd'] = [tmpl.cache['default']]
170 cmd = req.form['cmd'][0]
172 cmd = req.form['cmd'][0]
171
173
172 if cmd not in webcommands.__all__:
174 if cmd not in webcommands.__all__:
173 msg = 'no such method: %s' % cmd
175 msg = 'no such method: %s' % cmd
174 raise ErrorResponse(HTTP_BAD_REQUEST, msg)
176 raise ErrorResponse(HTTP_BAD_REQUEST, msg)
175 elif cmd == 'file' and 'raw' in req.form.get('style', []):
177 elif cmd == 'file' and 'raw' in req.form.get('style', []):
176 self.ctype = ctype
178 self.ctype = ctype
177 content = webcommands.rawfile(self, req, tmpl)
179 content = webcommands.rawfile(self, req, tmpl)
178 else:
180 else:
179 content = getattr(webcommands, cmd)(self, req, tmpl)
181 content = getattr(webcommands, cmd)(self, req, tmpl)
180 req.respond(HTTP_OK, ctype)
182 req.respond(HTTP_OK, ctype)
181
183
182 return content
184 return content
183
185
184 except error.LookupError, err:
186 except error.LookupError, err:
185 req.respond(HTTP_NOT_FOUND, ctype)
187 req.respond(HTTP_NOT_FOUND, ctype)
186 msg = str(err)
188 msg = str(err)
187 if 'manifest' not in msg:
189 if 'manifest' not in msg:
188 msg = 'revision not found: %s' % err.name
190 msg = 'revision not found: %s' % err.name
189 return tmpl('error', error=msg)
191 return tmpl('error', error=msg)
190 except (error.RepoError, error.RevlogError), inst:
192 except (error.RepoError, error.RevlogError), inst:
191 req.respond(HTTP_SERVER_ERROR, ctype)
193 req.respond(HTTP_SERVER_ERROR, ctype)
192 return tmpl('error', error=str(inst))
194 return tmpl('error', error=str(inst))
193 except ErrorResponse, inst:
195 except ErrorResponse, inst:
194 req.respond(inst, ctype)
196 req.respond(inst, ctype)
195 return tmpl('error', error=inst.message)
197 return tmpl('error', error=inst.message)
196
198
197 def templater(self, req):
199 def templater(self, req):
198
200
199 # determine scheme, port and server name
201 # determine scheme, port and server name
200 # this is needed to create absolute urls
202 # this is needed to create absolute urls
201
203
202 proto = req.env.get('wsgi.url_scheme')
204 proto = req.env.get('wsgi.url_scheme')
203 if proto == 'https':
205 if proto == 'https':
204 proto = 'https'
206 proto = 'https'
205 default_port = "443"
207 default_port = "443"
206 else:
208 else:
207 proto = 'http'
209 proto = 'http'
208 default_port = "80"
210 default_port = "80"
209
211
210 port = req.env["SERVER_PORT"]
212 port = req.env["SERVER_PORT"]
211 port = port != default_port and (":" + port) or ""
213 port = port != default_port and (":" + port) or ""
212 urlbase = '%s://%s%s' % (proto, req.env['SERVER_NAME'], port)
214 urlbase = '%s://%s%s' % (proto, req.env['SERVER_NAME'], port)
213 staticurl = self.config("web", "staticurl") or req.url + 'static/'
215 staticurl = self.config("web", "staticurl") or req.url + 'static/'
214 if not staticurl.endswith('/'):
216 if not staticurl.endswith('/'):
215 staticurl += '/'
217 staticurl += '/'
216
218
217 # some functions for the templater
219 # some functions for the templater
218
220
219 def header(**map):
221 def header(**map):
220 yield tmpl('header', encoding=self.encoding, **map)
222 yield tmpl('header', encoding=self.encoding, **map)
221
223
222 def footer(**map):
224 def footer(**map):
223 yield tmpl("footer", **map)
225 yield tmpl("footer", **map)
224
226
225 def motd(**map):
227 def motd(**map):
226 yield self.config("web", "motd", "")
228 yield self.config("web", "motd", "")
227
229
228 # figure out which style to use
230 # figure out which style to use
229
231
230 vars = {}
232 vars = {}
231 style = self.config("web", "style", "paper")
233 style = self.config("web", "style", "paper")
232 if 'style' in req.form:
234 if 'style' in req.form:
233 style = req.form['style'][0]
235 style = req.form['style'][0]
234 vars['style'] = style
236 vars['style'] = style
235
237
236 start = req.url[-1] == '?' and '&' or '?'
238 start = req.url[-1] == '?' and '&' or '?'
237 sessionvars = webutil.sessionvars(vars, start)
239 sessionvars = webutil.sessionvars(vars, start)
238 mapfile = templater.stylemap(style, self.templatepath)
240 mapfile = templater.stylemap(style, self.templatepath)
239
241
240 if not self.reponame:
242 if not self.reponame:
241 self.reponame = (self.config("web", "name")
243 self.reponame = (self.config("web", "name")
242 or req.env.get('REPO_NAME')
244 or req.env.get('REPO_NAME')
243 or req.url.strip('/') or self.repo.root)
245 or req.url.strip('/') or self.repo.root)
244
246
245 # create the templater
247 # create the templater
246
248
247 tmpl = templater.templater(mapfile, templatefilters.filters,
249 tmpl = templater.templater(mapfile, templatefilters.filters,
248 defaults={"url": req.url,
250 defaults={"url": req.url,
249 "staticurl": staticurl,
251 "staticurl": staticurl,
250 "urlbase": urlbase,
252 "urlbase": urlbase,
251 "repo": self.reponame,
253 "repo": self.reponame,
252 "header": header,
254 "header": header,
253 "footer": footer,
255 "footer": footer,
254 "motd": motd,
256 "motd": motd,
255 "sessionvars": sessionvars
257 "sessionvars": sessionvars
256 })
258 })
257 return tmpl
259 return tmpl
258
260
259 def archivelist(self, nodeid):
261 def archivelist(self, nodeid):
260 allowed = self.configlist("web", "allow_archive")
262 allowed = self.configlist("web", "allow_archive")
261 for i, spec in self.archive_specs.iteritems():
263 for i, spec in self.archive_specs.iteritems():
262 if i in allowed or self.configbool("web", "allow" + i):
264 if i in allowed or self.configbool("web", "allow" + i):
263 yield {"type" : i, "extension" : spec[2], "node" : nodeid}
265 yield {"type" : i, "extension" : spec[2], "node" : nodeid}
264
266
265 archive_specs = {
267 archive_specs = {
266 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', None),
268 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', None),
267 'gz': ('application/x-tar', 'tgz', '.tar.gz', None),
269 'gz': ('application/x-tar', 'tgz', '.tar.gz', None),
268 'zip': ('application/zip', 'zip', '.zip', None),
270 'zip': ('application/zip', 'zip', '.zip', None),
269 }
271 }
270
272
271 def check_perm(self, req, op):
273 def check_perm(self, req, op):
272 '''Check permission for operation based on request data (including
274 '''Check permission for operation based on request data (including
273 authentication info). Return if op allowed, else raise an ErrorResponse
275 authentication info). Return if op allowed, else raise an ErrorResponse
274 exception.'''
276 exception.'''
275
277
276 user = req.env.get('REMOTE_USER')
278 user = req.env.get('REMOTE_USER')
277
279
278 deny_read = self.configlist('web', 'deny_read')
280 deny_read = self.configlist('web', 'deny_read')
279 if deny_read and (not user or deny_read == ['*'] or user in deny_read):
281 if deny_read and (not user or deny_read == ['*'] or user in deny_read):
280 raise ErrorResponse(HTTP_UNAUTHORIZED, 'read not authorized')
282 raise ErrorResponse(HTTP_UNAUTHORIZED, 'read not authorized')
281
283
282 allow_read = self.configlist('web', 'allow_read')
284 allow_read = self.configlist('web', 'allow_read')
283 result = (not allow_read) or (allow_read == ['*'])
285 result = (not allow_read) or (allow_read == ['*'])
284 if not (result or user in allow_read):
286 if not (result or user in allow_read):
285 raise ErrorResponse(HTTP_UNAUTHORIZED, 'read not authorized')
287 raise ErrorResponse(HTTP_UNAUTHORIZED, 'read not authorized')
286
288
287 if op == 'pull' and not self.allowpull:
289 if op == 'pull' and not self.allowpull:
288 raise ErrorResponse(HTTP_UNAUTHORIZED, 'pull not authorized')
290 raise ErrorResponse(HTTP_UNAUTHORIZED, 'pull not authorized')
289 elif op == 'pull' or op is None: # op is None for interface requests
291 elif op == 'pull' or op is None: # op is None for interface requests
290 return
292 return
291
293
292 # enforce that you can only push using POST requests
294 # enforce that you can only push using POST requests
293 if req.env['REQUEST_METHOD'] != 'POST':
295 if req.env['REQUEST_METHOD'] != 'POST':
294 msg = 'push requires POST request'
296 msg = 'push requires POST request'
295 raise ErrorResponse(HTTP_METHOD_NOT_ALLOWED, msg)
297 raise ErrorResponse(HTTP_METHOD_NOT_ALLOWED, msg)
296
298
297 # require ssl by default for pushing, auth info cannot be sniffed
299 # require ssl by default for pushing, auth info cannot be sniffed
298 # and replayed
300 # and replayed
299 scheme = req.env.get('wsgi.url_scheme')
301 scheme = req.env.get('wsgi.url_scheme')
300 if self.configbool('web', 'push_ssl', True) and scheme != 'https':
302 if self.configbool('web', 'push_ssl', True) and scheme != 'https':
301 raise ErrorResponse(HTTP_OK, 'ssl required')
303 raise ErrorResponse(HTTP_OK, 'ssl required')
302
304
303 deny = self.configlist('web', 'deny_push')
305 deny = self.configlist('web', 'deny_push')
304 if deny and (not user or deny == ['*'] or user in deny):
306 if deny and (not user or deny == ['*'] or user in deny):
305 raise ErrorResponse(HTTP_UNAUTHORIZED, 'push not authorized')
307 raise ErrorResponse(HTTP_UNAUTHORIZED, 'push not authorized')
306
308
307 allow = self.configlist('web', 'allow_push')
309 allow = self.configlist('web', 'allow_push')
308 result = allow and (allow == ['*'] or user in allow)
310 result = allow and (allow == ['*'] or user in allow)
309 if not result:
311 if not result:
310 raise ErrorResponse(HTTP_UNAUTHORIZED, 'push not authorized')
312 raise ErrorResponse(HTTP_UNAUTHORIZED, 'push not authorized')
@@ -1,327 +1,332
1 # hgweb/hgwebdir_mod.py - Web interface for a directory of repositories.
1 # hgweb/hgwebdir_mod.py - Web interface for a directory of repositories.
2 #
2 #
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
5 #
5 #
6 # This software may be used and distributed according to the terms
6 # This software may be used and distributed according to the terms
7 # of the GNU General Public License, incorporated herein by reference.
7 # of the GNU General Public License, incorporated herein by reference.
8
8
9 import os
9 import os
10 from mercurial.i18n import _
10 from mercurial.i18n import _
11 from mercurial import ui, hg, util, templater, templatefilters, error, encoding
11 from mercurial import ui, hg, util, templater, templatefilters, error, encoding
12 from common import ErrorResponse, get_mtime, staticfile, paritygen,\
12 from common import ErrorResponse, get_mtime, staticfile, paritygen,\
13 get_contact, HTTP_OK, HTTP_NOT_FOUND, HTTP_SERVER_ERROR
13 get_contact, HTTP_OK, HTTP_NOT_FOUND, HTTP_SERVER_ERROR
14 from hgweb_mod import hgweb
14 from hgweb_mod import hgweb
15 from request import wsgirequest
15 from request import wsgirequest
16
16
17 # This is a stopgap
17 # This is a stopgap
18 class hgwebdir(object):
18 class hgwebdir(object):
19 def __init__(self, config, parentui=None):
19 def __init__(self, config, parentui=None):
20 def cleannames(items):
20 def cleannames(items):
21 return [(util.pconvert(name).strip('/'), path)
21 return [(util.pconvert(name).strip('/'), path)
22 for name, path in items]
22 for name, path in items]
23
23
24 self.parentui = parentui or ui.ui(report_untrusted=False,
24 if parentui:
25 interactive = False)
25 self.parentui = parentui
26 else:
27 self.parentui = ui.ui()
28 self.parentui.setconfig('ui', 'report_untrusted', 'off')
29 self.parentui.setconfig('ui', 'interactive', 'off')
30
26 self.motd = None
31 self.motd = None
27 self.style = 'paper'
32 self.style = 'paper'
28 self.stripecount = None
33 self.stripecount = None
29 self.repos_sorted = ('name', False)
34 self.repos_sorted = ('name', False)
30 self._baseurl = None
35 self._baseurl = None
31 if isinstance(config, (list, tuple)):
36 if isinstance(config, (list, tuple)):
32 self.repos = cleannames(config)
37 self.repos = cleannames(config)
33 self.repos_sorted = ('', False)
38 self.repos_sorted = ('', False)
34 elif isinstance(config, dict):
39 elif isinstance(config, dict):
35 self.repos = util.sort(cleannames(config.items()))
40 self.repos = util.sort(cleannames(config.items()))
36 else:
41 else:
37 if isinstance(config, util.configparser):
42 if isinstance(config, util.configparser):
38 cp = config
43 cp = config
39 else:
44 else:
40 cp = util.configparser()
45 cp = util.configparser()
41 cp.read(config)
46 cp.read(config)
42 self.repos = []
47 self.repos = []
43 if cp.has_section('web'):
48 if cp.has_section('web'):
44 if cp.has_option('web', 'motd'):
49 if cp.has_option('web', 'motd'):
45 self.motd = cp.get('web', 'motd')
50 self.motd = cp.get('web', 'motd')
46 if cp.has_option('web', 'style'):
51 if cp.has_option('web', 'style'):
47 self.style = cp.get('web', 'style')
52 self.style = cp.get('web', 'style')
48 if cp.has_option('web', 'stripes'):
53 if cp.has_option('web', 'stripes'):
49 self.stripecount = int(cp.get('web', 'stripes'))
54 self.stripecount = int(cp.get('web', 'stripes'))
50 if cp.has_option('web', 'baseurl'):
55 if cp.has_option('web', 'baseurl'):
51 self._baseurl = cp.get('web', 'baseurl')
56 self._baseurl = cp.get('web', 'baseurl')
52 if cp.has_section('paths'):
57 if cp.has_section('paths'):
53 paths = cleannames(cp.items('paths'))
58 paths = cleannames(cp.items('paths'))
54 for prefix, root in paths:
59 for prefix, root in paths:
55 roothead, roottail = os.path.split(root)
60 roothead, roottail = os.path.split(root)
56 # "foo = /bar/*" makes every subrepo of /bar/ to be
61 # "foo = /bar/*" makes every subrepo of /bar/ to be
57 # mounted as foo/subrepo
62 # mounted as foo/subrepo
58 # and "foo = /bar/**" does even recurse inside the
63 # and "foo = /bar/**" does even recurse inside the
59 # subdirectories, remember to use it without working dir.
64 # subdirectories, remember to use it without working dir.
60 try:
65 try:
61 recurse = {'*': False, '**': True}[roottail]
66 recurse = {'*': False, '**': True}[roottail]
62 except KeyError:
67 except KeyError:
63 self.repos.append((prefix, root))
68 self.repos.append((prefix, root))
64 continue
69 continue
65 roothead = os.path.normpath(roothead)
70 roothead = os.path.normpath(roothead)
66 for path in util.walkrepos(roothead, followsym=True,
71 for path in util.walkrepos(roothead, followsym=True,
67 recurse=recurse):
72 recurse=recurse):
68 path = os.path.normpath(path)
73 path = os.path.normpath(path)
69 name = util.pconvert(path[len(roothead):]).strip('/')
74 name = util.pconvert(path[len(roothead):]).strip('/')
70 if prefix:
75 if prefix:
71 name = prefix + '/' + name
76 name = prefix + '/' + name
72 self.repos.append((name, path))
77 self.repos.append((name, path))
73 if cp.has_section('collections'):
78 if cp.has_section('collections'):
74 for prefix, root in cp.items('collections'):
79 for prefix, root in cp.items('collections'):
75 for path in util.walkrepos(root, followsym=True):
80 for path in util.walkrepos(root, followsym=True):
76 repo = os.path.normpath(path)
81 repo = os.path.normpath(path)
77 name = repo
82 name = repo
78 if name.startswith(prefix):
83 if name.startswith(prefix):
79 name = name[len(prefix):]
84 name = name[len(prefix):]
80 self.repos.append((name.lstrip(os.sep), repo))
85 self.repos.append((name.lstrip(os.sep), repo))
81 self.repos.sort()
86 self.repos.sort()
82
87
83 def run(self):
88 def run(self):
84 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
89 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
85 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
90 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
86 import mercurial.hgweb.wsgicgi as wsgicgi
91 import mercurial.hgweb.wsgicgi as wsgicgi
87 wsgicgi.launch(self)
92 wsgicgi.launch(self)
88
93
89 def __call__(self, env, respond):
94 def __call__(self, env, respond):
90 req = wsgirequest(env, respond)
95 req = wsgirequest(env, respond)
91 return self.run_wsgi(req)
96 return self.run_wsgi(req)
92
97
93 def read_allowed(self, ui, req):
98 def read_allowed(self, ui, req):
94 """Check allow_read and deny_read config options of a repo's ui object
99 """Check allow_read and deny_read config options of a repo's ui object
95 to determine user permissions. By default, with neither option set (or
100 to determine user permissions. By default, with neither option set (or
96 both empty), allow all users to read the repo. There are two ways a
101 both empty), allow all users to read the repo. There are two ways a
97 user can be denied read access: (1) deny_read is not empty, and the
102 user can be denied read access: (1) deny_read is not empty, and the
98 user is unauthenticated or deny_read contains user (or *), and (2)
103 user is unauthenticated or deny_read contains user (or *), and (2)
99 allow_read is not empty and the user is not in allow_read. Return True
104 allow_read is not empty and the user is not in allow_read. Return True
100 if user is allowed to read the repo, else return False."""
105 if user is allowed to read the repo, else return False."""
101
106
102 user = req.env.get('REMOTE_USER')
107 user = req.env.get('REMOTE_USER')
103
108
104 deny_read = ui.configlist('web', 'deny_read', untrusted=True)
109 deny_read = ui.configlist('web', 'deny_read', untrusted=True)
105 if deny_read and (not user or deny_read == ['*'] or user in deny_read):
110 if deny_read and (not user or deny_read == ['*'] or user in deny_read):
106 return False
111 return False
107
112
108 allow_read = ui.configlist('web', 'allow_read', untrusted=True)
113 allow_read = ui.configlist('web', 'allow_read', untrusted=True)
109 # by default, allow reading if no allow_read option has been set
114 # by default, allow reading if no allow_read option has been set
110 if (not allow_read) or (allow_read == ['*']) or (user in allow_read):
115 if (not allow_read) or (allow_read == ['*']) or (user in allow_read):
111 return True
116 return True
112
117
113 return False
118 return False
114
119
115 def run_wsgi(self, req):
120 def run_wsgi(self, req):
116
121
117 try:
122 try:
118 try:
123 try:
119
124
120 virtual = req.env.get("PATH_INFO", "").strip('/')
125 virtual = req.env.get("PATH_INFO", "").strip('/')
121 tmpl = self.templater(req)
126 tmpl = self.templater(req)
122 ctype = tmpl('mimetype', encoding=encoding.encoding)
127 ctype = tmpl('mimetype', encoding=encoding.encoding)
123 ctype = templater.stringify(ctype)
128 ctype = templater.stringify(ctype)
124
129
125 # a static file
130 # a static file
126 if virtual.startswith('static/') or 'static' in req.form:
131 if virtual.startswith('static/') or 'static' in req.form:
127 if virtual.startswith('static/'):
132 if virtual.startswith('static/'):
128 fname = virtual[7:]
133 fname = virtual[7:]
129 else:
134 else:
130 fname = req.form['static'][0]
135 fname = req.form['static'][0]
131 static = templater.templatepath('static')
136 static = templater.templatepath('static')
132 return (staticfile(static, fname, req),)
137 return (staticfile(static, fname, req),)
133
138
134 # top-level index
139 # top-level index
135 elif not virtual:
140 elif not virtual:
136 req.respond(HTTP_OK, ctype)
141 req.respond(HTTP_OK, ctype)
137 return self.makeindex(req, tmpl)
142 return self.makeindex(req, tmpl)
138
143
139 # nested indexes and hgwebs
144 # nested indexes and hgwebs
140
145
141 repos = dict(self.repos)
146 repos = dict(self.repos)
142 while virtual:
147 while virtual:
143 real = repos.get(virtual)
148 real = repos.get(virtual)
144 if real:
149 if real:
145 req.env['REPO_NAME'] = virtual
150 req.env['REPO_NAME'] = virtual
146 try:
151 try:
147 repo = hg.repository(self.parentui, real)
152 repo = hg.repository(self.parentui, real)
148 return hgweb(repo).run_wsgi(req)
153 return hgweb(repo).run_wsgi(req)
149 except IOError, inst:
154 except IOError, inst:
150 msg = inst.strerror
155 msg = inst.strerror
151 raise ErrorResponse(HTTP_SERVER_ERROR, msg)
156 raise ErrorResponse(HTTP_SERVER_ERROR, msg)
152 except error.RepoError, inst:
157 except error.RepoError, inst:
153 raise ErrorResponse(HTTP_SERVER_ERROR, str(inst))
158 raise ErrorResponse(HTTP_SERVER_ERROR, str(inst))
154
159
155 # browse subdirectories
160 # browse subdirectories
156 subdir = virtual + '/'
161 subdir = virtual + '/'
157 if [r for r in repos if r.startswith(subdir)]:
162 if [r for r in repos if r.startswith(subdir)]:
158 req.respond(HTTP_OK, ctype)
163 req.respond(HTTP_OK, ctype)
159 return self.makeindex(req, tmpl, subdir)
164 return self.makeindex(req, tmpl, subdir)
160
165
161 up = virtual.rfind('/')
166 up = virtual.rfind('/')
162 if up < 0:
167 if up < 0:
163 break
168 break
164 virtual = virtual[:up]
169 virtual = virtual[:up]
165
170
166 # prefixes not found
171 # prefixes not found
167 req.respond(HTTP_NOT_FOUND, ctype)
172 req.respond(HTTP_NOT_FOUND, ctype)
168 return tmpl("notfound", repo=virtual)
173 return tmpl("notfound", repo=virtual)
169
174
170 except ErrorResponse, err:
175 except ErrorResponse, err:
171 req.respond(err, ctype)
176 req.respond(err, ctype)
172 return tmpl('error', error=err.message or '')
177 return tmpl('error', error=err.message or '')
173 finally:
178 finally:
174 tmpl = None
179 tmpl = None
175
180
176 def makeindex(self, req, tmpl, subdir=""):
181 def makeindex(self, req, tmpl, subdir=""):
177
182
178 def archivelist(ui, nodeid, url):
183 def archivelist(ui, nodeid, url):
179 allowed = ui.configlist("web", "allow_archive", untrusted=True)
184 allowed = ui.configlist("web", "allow_archive", untrusted=True)
180 for i in [('zip', '.zip'), ('gz', '.tar.gz'), ('bz2', '.tar.bz2')]:
185 for i in [('zip', '.zip'), ('gz', '.tar.gz'), ('bz2', '.tar.bz2')]:
181 if i[0] in allowed or ui.configbool("web", "allow" + i[0],
186 if i[0] in allowed or ui.configbool("web", "allow" + i[0],
182 untrusted=True):
187 untrusted=True):
183 yield {"type" : i[0], "extension": i[1],
188 yield {"type" : i[0], "extension": i[1],
184 "node": nodeid, "url": url}
189 "node": nodeid, "url": url}
185
190
186 def entries(sortcolumn="", descending=False, subdir="", **map):
191 def entries(sortcolumn="", descending=False, subdir="", **map):
187 def sessionvars(**map):
192 def sessionvars(**map):
188 fields = []
193 fields = []
189 if 'style' in req.form:
194 if 'style' in req.form:
190 style = req.form['style'][0]
195 style = req.form['style'][0]
191 if style != get('web', 'style', ''):
196 if style != get('web', 'style', ''):
192 fields.append(('style', style))
197 fields.append(('style', style))
193
198
194 separator = url[-1] == '?' and ';' or '?'
199 separator = url[-1] == '?' and ';' or '?'
195 for name, value in fields:
200 for name, value in fields:
196 yield dict(name=name, value=value, separator=separator)
201 yield dict(name=name, value=value, separator=separator)
197 separator = ';'
202 separator = ';'
198
203
199 rows = []
204 rows = []
200 parity = paritygen(self.stripecount)
205 parity = paritygen(self.stripecount)
201 for name, path in self.repos:
206 for name, path in self.repos:
202 if not name.startswith(subdir):
207 if not name.startswith(subdir):
203 continue
208 continue
204 name = name[len(subdir):]
209 name = name[len(subdir):]
205
210
206 u = ui.ui(parentui=self.parentui)
211 u = ui.ui(parentui=self.parentui)
207 try:
212 try:
208 u.readconfig(os.path.join(path, '.hg', 'hgrc'))
213 u.readconfig(os.path.join(path, '.hg', 'hgrc'))
209 except Exception, e:
214 except Exception, e:
210 u.warn(_('error reading %s/.hg/hgrc: %s\n') % (path, e))
215 u.warn(_('error reading %s/.hg/hgrc: %s\n') % (path, e))
211 continue
216 continue
212 def get(section, name, default=None):
217 def get(section, name, default=None):
213 return u.config(section, name, default, untrusted=True)
218 return u.config(section, name, default, untrusted=True)
214
219
215 if u.configbool("web", "hidden", untrusted=True):
220 if u.configbool("web", "hidden", untrusted=True):
216 continue
221 continue
217
222
218 if not self.read_allowed(u, req):
223 if not self.read_allowed(u, req):
219 continue
224 continue
220
225
221 parts = [name]
226 parts = [name]
222 if 'PATH_INFO' in req.env:
227 if 'PATH_INFO' in req.env:
223 parts.insert(0, req.env['PATH_INFO'].rstrip('/'))
228 parts.insert(0, req.env['PATH_INFO'].rstrip('/'))
224 if req.env['SCRIPT_NAME']:
229 if req.env['SCRIPT_NAME']:
225 parts.insert(0, req.env['SCRIPT_NAME'])
230 parts.insert(0, req.env['SCRIPT_NAME'])
226 url = ('/'.join(parts).replace("//", "/")) + '/'
231 url = ('/'.join(parts).replace("//", "/")) + '/'
227
232
228 # update time with local timezone
233 # update time with local timezone
229 try:
234 try:
230 d = (get_mtime(path), util.makedate()[1])
235 d = (get_mtime(path), util.makedate()[1])
231 except OSError:
236 except OSError:
232 continue
237 continue
233
238
234 contact = get_contact(get)
239 contact = get_contact(get)
235 description = get("web", "description", "")
240 description = get("web", "description", "")
236 name = get("web", "name", name)
241 name = get("web", "name", name)
237 row = dict(contact=contact or "unknown",
242 row = dict(contact=contact or "unknown",
238 contact_sort=contact.upper() or "unknown",
243 contact_sort=contact.upper() or "unknown",
239 name=name,
244 name=name,
240 name_sort=name,
245 name_sort=name,
241 url=url,
246 url=url,
242 description=description or "unknown",
247 description=description or "unknown",
243 description_sort=description.upper() or "unknown",
248 description_sort=description.upper() or "unknown",
244 lastchange=d,
249 lastchange=d,
245 lastchange_sort=d[1]-d[0],
250 lastchange_sort=d[1]-d[0],
246 sessionvars=sessionvars,
251 sessionvars=sessionvars,
247 archives=archivelist(u, "tip", url))
252 archives=archivelist(u, "tip", url))
248 if (not sortcolumn
253 if (not sortcolumn
249 or (sortcolumn, descending) == self.repos_sorted):
254 or (sortcolumn, descending) == self.repos_sorted):
250 # fast path for unsorted output
255 # fast path for unsorted output
251 row['parity'] = parity.next()
256 row['parity'] = parity.next()
252 yield row
257 yield row
253 else:
258 else:
254 rows.append((row["%s_sort" % sortcolumn], row))
259 rows.append((row["%s_sort" % sortcolumn], row))
255 if rows:
260 if rows:
256 rows.sort()
261 rows.sort()
257 if descending:
262 if descending:
258 rows.reverse()
263 rows.reverse()
259 for key, row in rows:
264 for key, row in rows:
260 row['parity'] = parity.next()
265 row['parity'] = parity.next()
261 yield row
266 yield row
262
267
263 sortable = ["name", "description", "contact", "lastchange"]
268 sortable = ["name", "description", "contact", "lastchange"]
264 sortcolumn, descending = self.repos_sorted
269 sortcolumn, descending = self.repos_sorted
265 if 'sort' in req.form:
270 if 'sort' in req.form:
266 sortcolumn = req.form['sort'][0]
271 sortcolumn = req.form['sort'][0]
267 descending = sortcolumn.startswith('-')
272 descending = sortcolumn.startswith('-')
268 if descending:
273 if descending:
269 sortcolumn = sortcolumn[1:]
274 sortcolumn = sortcolumn[1:]
270 if sortcolumn not in sortable:
275 if sortcolumn not in sortable:
271 sortcolumn = ""
276 sortcolumn = ""
272
277
273 sort = [("sort_%s" % column,
278 sort = [("sort_%s" % column,
274 "%s%s" % ((not descending and column == sortcolumn)
279 "%s%s" % ((not descending and column == sortcolumn)
275 and "-" or "", column))
280 and "-" or "", column))
276 for column in sortable]
281 for column in sortable]
277
282
278 if self._baseurl is not None:
283 if self._baseurl is not None:
279 req.env['SCRIPT_NAME'] = self._baseurl
284 req.env['SCRIPT_NAME'] = self._baseurl
280
285
281 return tmpl("index", entries=entries, subdir=subdir,
286 return tmpl("index", entries=entries, subdir=subdir,
282 sortcolumn=sortcolumn, descending=descending,
287 sortcolumn=sortcolumn, descending=descending,
283 **dict(sort))
288 **dict(sort))
284
289
285 def templater(self, req):
290 def templater(self, req):
286
291
287 def header(**map):
292 def header(**map):
288 yield tmpl('header', encoding=encoding.encoding, **map)
293 yield tmpl('header', encoding=encoding.encoding, **map)
289
294
290 def footer(**map):
295 def footer(**map):
291 yield tmpl("footer", **map)
296 yield tmpl("footer", **map)
292
297
293 def motd(**map):
298 def motd(**map):
294 if self.motd is not None:
299 if self.motd is not None:
295 yield self.motd
300 yield self.motd
296 else:
301 else:
297 yield config('web', 'motd', '')
302 yield config('web', 'motd', '')
298
303
299 def config(section, name, default=None, untrusted=True):
304 def config(section, name, default=None, untrusted=True):
300 return self.parentui.config(section, name, default, untrusted)
305 return self.parentui.config(section, name, default, untrusted)
301
306
302 if self._baseurl is not None:
307 if self._baseurl is not None:
303 req.env['SCRIPT_NAME'] = self._baseurl
308 req.env['SCRIPT_NAME'] = self._baseurl
304
309
305 url = req.env.get('SCRIPT_NAME', '')
310 url = req.env.get('SCRIPT_NAME', '')
306 if not url.endswith('/'):
311 if not url.endswith('/'):
307 url += '/'
312 url += '/'
308
313
309 staticurl = config('web', 'staticurl') or url + 'static/'
314 staticurl = config('web', 'staticurl') or url + 'static/'
310 if not staticurl.endswith('/'):
315 if not staticurl.endswith('/'):
311 staticurl += '/'
316 staticurl += '/'
312
317
313 style = self.style
318 style = self.style
314 if style is None:
319 if style is None:
315 style = config('web', 'style', '')
320 style = config('web', 'style', '')
316 if 'style' in req.form:
321 if 'style' in req.form:
317 style = req.form['style'][0]
322 style = req.form['style'][0]
318 if self.stripecount is None:
323 if self.stripecount is None:
319 self.stripecount = int(config('web', 'stripes', 1))
324 self.stripecount = int(config('web', 'stripes', 1))
320 mapfile = templater.stylemap(style)
325 mapfile = templater.stylemap(style)
321 tmpl = templater.templater(mapfile, templatefilters.filters,
326 tmpl = templater.templater(mapfile, templatefilters.filters,
322 defaults={"header": header,
327 defaults={"header": header,
323 "footer": footer,
328 "footer": footer,
324 "motd": motd,
329 "motd": motd,
325 "url": url,
330 "url": url,
326 "staticurl": staticurl})
331 "staticurl": staticurl})
327 return tmpl
332 return tmpl
@@ -1,478 +1,464
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
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7
7
8 from i18n import _
8 from i18n import _
9 import errno, getpass, os, re, socket, sys, tempfile
9 import errno, getpass, os, re, socket, sys, tempfile
10 import ConfigParser, traceback, util
10 import ConfigParser, traceback, util
11
11
12 def dupconfig(orig):
12 def dupconfig(orig):
13 new = util.configparser(orig.defaults())
13 new = util.configparser(orig.defaults())
14 updateconfig(orig, new)
14 updateconfig(orig, new)
15 return new
15 return new
16
16
17 def updateconfig(source, dest, sections=None):
17 def updateconfig(source, dest, sections=None):
18 if not sections:
18 if not sections:
19 sections = source.sections()
19 sections = source.sections()
20 for section in sections:
20 for section in sections:
21 if not dest.has_section(section):
21 if not dest.has_section(section):
22 dest.add_section(section)
22 dest.add_section(section)
23 for name, value in source.items(section, raw=True):
23 for name, value in source.items(section, raw=True):
24 dest.set(section, name, value)
24 dest.set(section, name, value)
25
25
26 class ui(object):
26 class ui(object):
27 def __init__(self, verbose=False, debug=False, quiet=False,
27 def __init__(self, parentui=None):
28 interactive=True, traceback=False, report_untrusted=True,
29 parentui=None):
30 self.buffers = []
28 self.buffers = []
29 self.quiet = self.verbose = self.debugflag = self.traceback = False
30 self.interactive = self.report_untrusted = True
31
31 if parentui is None:
32 if parentui is None:
32 # this is the parent of all ui children
33 # this is the parent of all ui children
33 self.parentui = None
34 self.parentui = None
34 self.quiet = quiet
35 self.verbose = verbose
36 self.debugflag = debug
37 self.interactive = interactive
38 self.traceback = traceback
39 self.report_untrusted = report_untrusted
40 self.trusted_users = {}
35 self.trusted_users = {}
41 self.trusted_groups = {}
36 self.trusted_groups = {}
42 self.overlay = util.configparser()
37 self.overlay = util.configparser()
43 # if ucdata is not None, its keys must be a superset of cdata's
38 # if ucdata is not None, its keys must be a superset of cdata's
44 self.cdata = util.configparser()
39 self.cdata = util.configparser()
45 self.ucdata = None
40 self.ucdata = None
46 # we always trust global config files
41 # we always trust global config files
47 self.readconfig(util.rcpath(), assumetrusted=True)
42 self.readconfig(util.rcpath(), assumetrusted=True)
48 self.updateopts(verbose, debug, quiet, interactive)
49 else:
43 else:
50 # parentui may point to an ui object which is already a child
44 # parentui may point to an ui object which is already a child
51 self.parentui = parentui.parentui or parentui
45 self.parentui = parentui.parentui or parentui
52 self.trusted_users = parentui.trusted_users.copy()
46 self.trusted_users = parentui.trusted_users.copy()
53 self.trusted_groups = parentui.trusted_groups.copy()
47 self.trusted_groups = parentui.trusted_groups.copy()
54 self.cdata = dupconfig(self.parentui.cdata)
48 self.cdata = dupconfig(self.parentui.cdata)
55 self.overlay = dupconfig(self.parentui.overlay)
49 self.overlay = dupconfig(self.parentui.overlay)
56 if self.parentui.ucdata:
50 if self.parentui.ucdata:
57 self.ucdata = dupconfig(self.parentui.ucdata)
51 self.ucdata = dupconfig(self.parentui.ucdata)
58 if self.parentui is not parentui:
52 if self.parentui is not parentui:
59 self.overlay = util.configparser()
53 self.overlay = util.configparser()
60 updateconfig(parentui.overlay, self.overlay)
54 updateconfig(parentui.overlay, self.overlay)
61 self.buffers = parentui.buffers
55 self.buffers = parentui.buffers
62
56
63 def __getattr__(self, key):
57 def __getattr__(self, key):
64 return getattr(self.parentui, key)
58 return getattr(self.parentui, key)
65
59
66 _isatty = None
60 _isatty = None
67 def isatty(self):
61 def isatty(self):
68 if ui._isatty is None:
62 if ui._isatty is None:
63 try:
69 ui._isatty = sys.stdin.isatty()
64 ui._isatty = sys.stdin.isatty()
65 except AttributeError: # not a real file object
66 ui._isatty = False
70 return ui._isatty
67 return ui._isatty
71
68
72 def updateopts(self, verbose=False, debug=False, quiet=False,
69 def updateopts(self, config):
73 interactive=True, traceback=False, config=[]):
74 for section, name, value in config:
70 for section, name, value in config:
75 self.setconfig(section, name, value)
71 self.setconfig(section, name, value)
76
72
77 if quiet or verbose or debug:
78 self.setconfig('ui', 'quiet', str(bool(quiet)))
79 self.setconfig('ui', 'verbose', str(bool(verbose)))
80 self.setconfig('ui', 'debug', str(bool(debug)))
81
82 if not interactive:
83 self.setconfig('ui', 'interactive', 'False')
84 self.interactive = False
85
86 self.traceback = self.traceback or traceback
87
88 def verbosity_constraints(self):
73 def verbosity_constraints(self):
89 self.quiet = self.configbool('ui', 'quiet')
74 self.quiet = self.configbool('ui', 'quiet')
90 self.verbose = self.configbool('ui', 'verbose')
75 self.verbose = self.configbool('ui', 'verbose')
91 self.debugflag = self.configbool('ui', 'debug')
76 self.debugflag = self.configbool('ui', 'debug')
92
77
93 if self.debugflag:
78 if self.debugflag:
94 self.verbose = True
79 self.verbose = True
95 self.quiet = False
80 self.quiet = False
96 elif self.verbose and self.quiet:
81 elif self.verbose and self.quiet:
97 self.quiet = self.verbose = False
82 self.quiet = self.verbose = False
98
83
99 def _is_trusted(self, fp, f, warn=True):
84 def _is_trusted(self, fp, f, warn=True):
100 st = util.fstat(fp)
85 st = util.fstat(fp)
101 if util.isowner(fp, st):
86 if util.isowner(fp, st):
102 return True
87 return True
103 tusers = self.trusted_users
88 tusers = self.trusted_users
104 tgroups = self.trusted_groups
89 tgroups = self.trusted_groups
105 if not tusers:
90 if not tusers:
106 user = util.username()
91 user = util.username()
107 if user is not None:
92 if user is not None:
108 self.trusted_users[user] = 1
93 self.trusted_users[user] = 1
109 self.fixconfig(section='trusted')
94 self.fixconfig(section='trusted')
110 if (tusers or tgroups) and '*' not in tusers and '*' not in tgroups:
95 if (tusers or tgroups) and '*' not in tusers and '*' not in tgroups:
111 user = util.username(st.st_uid)
96 user = util.username(st.st_uid)
112 group = util.groupname(st.st_gid)
97 group = util.groupname(st.st_gid)
113 if user not in tusers and group not in tgroups:
98 if user not in tusers and group not in tgroups:
114 if warn and self.report_untrusted:
99 if warn and self.report_untrusted:
115 self.warn(_('Not trusting file %s from untrusted '
100 self.warn(_('Not trusting file %s from untrusted '
116 'user %s, group %s\n') % (f, user, group))
101 'user %s, group %s\n') % (f, user, group))
117 return False
102 return False
118 return True
103 return True
119
104
120 def readconfig(self, fn, root=None, assumetrusted=False):
105 def readconfig(self, fn, root=None, assumetrusted=False):
121 if isinstance(fn, basestring):
106 if isinstance(fn, basestring):
122 fn = [fn]
107 fn = [fn]
123 for f in fn:
108 for f in fn:
124 try:
109 try:
125 fp = open(f)
110 fp = open(f)
126 except IOError:
111 except IOError:
127 continue
112 continue
128 cdata = self.cdata
113 cdata = self.cdata
129 trusted = assumetrusted or self._is_trusted(fp, f)
114 trusted = assumetrusted or self._is_trusted(fp, f)
130 if not trusted:
115 if not trusted:
131 if self.ucdata is None:
116 if self.ucdata is None:
132 self.ucdata = dupconfig(self.cdata)
117 self.ucdata = dupconfig(self.cdata)
133 cdata = self.ucdata
118 cdata = self.ucdata
134 elif self.ucdata is not None:
119 elif self.ucdata is not None:
135 # use a separate configparser, so that we don't accidentally
120 # use a separate configparser, so that we don't accidentally
136 # override ucdata settings later on.
121 # override ucdata settings later on.
137 cdata = util.configparser()
122 cdata = util.configparser()
138
123
139 try:
124 try:
140 cdata.readfp(fp, f)
125 cdata.readfp(fp, f)
141 except ConfigParser.ParsingError, inst:
126 except ConfigParser.ParsingError, inst:
142 msg = _("Failed to parse %s\n%s") % (f, inst)
127 msg = _("Failed to parse %s\n%s") % (f, inst)
143 if trusted:
128 if trusted:
144 raise util.Abort(msg)
129 raise util.Abort(msg)
145 self.warn(_("Ignored: %s\n") % msg)
130 self.warn(_("Ignored: %s\n") % msg)
146
131
147 if trusted:
132 if trusted:
148 if cdata != self.cdata:
133 if cdata != self.cdata:
149 updateconfig(cdata, self.cdata)
134 updateconfig(cdata, self.cdata)
150 if self.ucdata is not None:
135 if self.ucdata is not None:
151 updateconfig(cdata, self.ucdata)
136 updateconfig(cdata, self.ucdata)
152 # override data from config files with data set with ui.setconfig
137 # override data from config files with data set with ui.setconfig
153 updateconfig(self.overlay, self.cdata)
138 updateconfig(self.overlay, self.cdata)
154 if root is None:
139 if root is None:
155 root = os.path.expanduser('~')
140 root = os.path.expanduser('~')
156 self.fixconfig(root=root)
141 self.fixconfig(root=root)
157
142
158 def readsections(self, filename, *sections):
143 def readsections(self, filename, *sections):
159 """Read filename and add only the specified sections to the config data
144 """Read filename and add only the specified sections to the config data
160
145
161 The settings are added to the trusted config data.
146 The settings are added to the trusted config data.
162 """
147 """
163 if not sections:
148 if not sections:
164 return
149 return
165
150
166 cdata = util.configparser()
151 cdata = util.configparser()
167 try:
152 try:
168 try:
153 try:
169 fp = open(filename)
154 fp = open(filename)
170 except IOError, inst:
155 except IOError, inst:
171 raise util.Abort(_("unable to open %s: %s") %
156 raise util.Abort(_("unable to open %s: %s") %
172 (filename, getattr(inst, "strerror", inst)))
157 (filename, getattr(inst, "strerror", inst)))
173 try:
158 try:
174 cdata.readfp(fp, filename)
159 cdata.readfp(fp, filename)
175 finally:
160 finally:
176 fp.close()
161 fp.close()
177 except ConfigParser.ParsingError, inst:
162 except ConfigParser.ParsingError, inst:
178 raise util.Abort(_("failed to parse %s\n%s") % (filename, inst))
163 raise util.Abort(_("failed to parse %s\n%s") % (filename, inst))
179
164
180 for section in sections:
165 for section in sections:
181 if not cdata.has_section(section):
166 if not cdata.has_section(section):
182 cdata.add_section(section)
167 cdata.add_section(section)
183
168
184 updateconfig(cdata, self.cdata, sections)
169 updateconfig(cdata, self.cdata, sections)
185 if self.ucdata:
170 if self.ucdata:
186 updateconfig(cdata, self.ucdata, sections)
171 updateconfig(cdata, self.ucdata, sections)
187
172
188 def fixconfig(self, section=None, name=None, value=None, root=None):
173 def fixconfig(self, section=None, name=None, value=None, root=None):
189 # translate paths relative to root (or home) into absolute paths
174 # translate paths relative to root (or home) into absolute paths
190 if section is None or section == 'paths':
175 if section is None or section == 'paths':
191 if root is None:
176 if root is None:
192 root = os.getcwd()
177 root = os.getcwd()
193 items = section and [(name, value)] or []
178 items = section and [(name, value)] or []
194 for cdata in self.cdata, self.ucdata, self.overlay:
179 for cdata in self.cdata, self.ucdata, self.overlay:
195 if not cdata: continue
180 if not cdata: continue
196 if not items and cdata.has_section('paths'):
181 if not items and cdata.has_section('paths'):
197 pathsitems = cdata.items('paths')
182 pathsitems = cdata.items('paths')
198 else:
183 else:
199 pathsitems = items
184 pathsitems = items
200 for n, path in pathsitems:
185 for n, path in pathsitems:
201 if path and "://" not in path and not os.path.isabs(path):
186 if path and "://" not in path and not os.path.isabs(path):
202 cdata.set("paths", n,
187 cdata.set("paths", n,
203 os.path.normpath(os.path.join(root, path)))
188 os.path.normpath(os.path.join(root, path)))
204
189
205 # update verbosity/interactive/report_untrusted settings
190 # update verbosity/interactive/report_untrusted settings
206 if section is None or section == 'ui':
191 if section is None or section == 'ui':
207 if name is None or name in ('quiet', 'verbose', 'debug'):
192 if name is None or name in ('quiet', 'verbose', 'debug'):
208 self.verbosity_constraints()
193 self.verbosity_constraints()
209 if name is None or name == 'interactive':
194 if name is None or name == 'interactive':
210 interactive = self.configbool("ui", "interactive", None)
195 interactive = self.configbool("ui", "interactive", None)
211 if interactive is None and self.interactive:
196 if interactive is None and self.interactive:
212 self.interactive = self.isatty()
197 self.interactive = self.isatty()
213 else:
198 else:
214 self.interactive = interactive
199 self.interactive = interactive
215 if name is None or name == 'report_untrusted':
200 if name is None or name == 'report_untrusted':
216 self.report_untrusted = (
201 self.report_untrusted = (
217 self.configbool("ui", "report_untrusted", True))
202 self.configbool("ui", "report_untrusted", True))
203 self.traceback = self.configbool('ui', 'traceback', False)
218
204
219 # update trust information
205 # update trust information
220 if (section is None or section == 'trusted') and self.trusted_users:
206 if (section is None or section == 'trusted') and self.trusted_users:
221 for user in self.configlist('trusted', 'users'):
207 for user in self.configlist('trusted', 'users'):
222 self.trusted_users[user] = 1
208 self.trusted_users[user] = 1
223 for group in self.configlist('trusted', 'groups'):
209 for group in self.configlist('trusted', 'groups'):
224 self.trusted_groups[group] = 1
210 self.trusted_groups[group] = 1
225
211
226 def setconfig(self, section, name, value):
212 def setconfig(self, section, name, value):
227 for cdata in (self.overlay, self.cdata, self.ucdata):
213 for cdata in (self.overlay, self.cdata, self.ucdata):
228 if not cdata: continue
214 if not cdata: continue
229 if not cdata.has_section(section):
215 if not cdata.has_section(section):
230 cdata.add_section(section)
216 cdata.add_section(section)
231 cdata.set(section, name, value)
217 cdata.set(section, name, value)
232 self.fixconfig(section, name, value)
218 self.fixconfig(section, name, value)
233
219
234 def _get_cdata(self, untrusted):
220 def _get_cdata(self, untrusted):
235 if untrusted and self.ucdata:
221 if untrusted and self.ucdata:
236 return self.ucdata
222 return self.ucdata
237 return self.cdata
223 return self.cdata
238
224
239 def _config(self, section, name, default, funcname, untrusted, abort):
225 def _config(self, section, name, default, funcname, untrusted, abort):
240 cdata = self._get_cdata(untrusted)
226 cdata = self._get_cdata(untrusted)
241 if cdata.has_option(section, name):
227 if cdata.has_option(section, name):
242 try:
228 try:
243 func = getattr(cdata, funcname)
229 func = getattr(cdata, funcname)
244 return func(section, name)
230 return func(section, name)
245 except (ConfigParser.InterpolationError, ValueError), inst:
231 except (ConfigParser.InterpolationError, ValueError), inst:
246 msg = _("Error in configuration section [%s] "
232 msg = _("Error in configuration section [%s] "
247 "parameter '%s':\n%s") % (section, name, inst)
233 "parameter '%s':\n%s") % (section, name, inst)
248 if abort:
234 if abort:
249 raise util.Abort(msg)
235 raise util.Abort(msg)
250 self.warn(_("Ignored: %s\n") % msg)
236 self.warn(_("Ignored: %s\n") % msg)
251 return default
237 return default
252
238
253 def _configcommon(self, section, name, default, funcname, untrusted):
239 def _configcommon(self, section, name, default, funcname, untrusted):
254 value = self._config(section, name, default, funcname,
240 value = self._config(section, name, default, funcname,
255 untrusted, abort=True)
241 untrusted, abort=True)
256 if self.debugflag and not untrusted and self.ucdata:
242 if self.debugflag and not untrusted and self.ucdata:
257 uvalue = self._config(section, name, None, funcname,
243 uvalue = self._config(section, name, None, funcname,
258 untrusted=True, abort=False)
244 untrusted=True, abort=False)
259 if uvalue is not None and uvalue != value:
245 if uvalue is not None and uvalue != value:
260 self.warn(_("Ignoring untrusted configuration option "
246 self.warn(_("Ignoring untrusted configuration option "
261 "%s.%s = %s\n") % (section, name, uvalue))
247 "%s.%s = %s\n") % (section, name, uvalue))
262 return value
248 return value
263
249
264 def config(self, section, name, default=None, untrusted=False):
250 def config(self, section, name, default=None, untrusted=False):
265 return self._configcommon(section, name, default, 'get', untrusted)
251 return self._configcommon(section, name, default, 'get', untrusted)
266
252
267 def configbool(self, section, name, default=False, untrusted=False):
253 def configbool(self, section, name, default=False, untrusted=False):
268 return self._configcommon(section, name, default, 'getboolean',
254 return self._configcommon(section, name, default, 'getboolean',
269 untrusted)
255 untrusted)
270
256
271 def configlist(self, section, name, default=None, untrusted=False):
257 def configlist(self, section, name, default=None, untrusted=False):
272 """Return a list of comma/space separated strings"""
258 """Return a list of comma/space separated strings"""
273 result = self.config(section, name, untrusted=untrusted)
259 result = self.config(section, name, untrusted=untrusted)
274 if result is None:
260 if result is None:
275 result = default or []
261 result = default or []
276 if isinstance(result, basestring):
262 if isinstance(result, basestring):
277 result = result.replace(",", " ").split()
263 result = result.replace(",", " ").split()
278 return result
264 return result
279
265
280 def has_section(self, section, untrusted=False):
266 def has_section(self, section, untrusted=False):
281 '''tell whether section exists in config.'''
267 '''tell whether section exists in config.'''
282 cdata = self._get_cdata(untrusted)
268 cdata = self._get_cdata(untrusted)
283 return cdata.has_section(section)
269 return cdata.has_section(section)
284
270
285 def _configitems(self, section, untrusted, abort):
271 def _configitems(self, section, untrusted, abort):
286 items = {}
272 items = {}
287 cdata = self._get_cdata(untrusted)
273 cdata = self._get_cdata(untrusted)
288 if cdata.has_section(section):
274 if cdata.has_section(section):
289 try:
275 try:
290 items.update(dict(cdata.items(section)))
276 items.update(dict(cdata.items(section)))
291 except ConfigParser.InterpolationError, inst:
277 except ConfigParser.InterpolationError, inst:
292 msg = _("Error in configuration section [%s]:\n"
278 msg = _("Error in configuration section [%s]:\n"
293 "%s") % (section, inst)
279 "%s") % (section, inst)
294 if abort:
280 if abort:
295 raise util.Abort(msg)
281 raise util.Abort(msg)
296 self.warn(_("Ignored: %s\n") % msg)
282 self.warn(_("Ignored: %s\n") % msg)
297 return items
283 return items
298
284
299 def configitems(self, section, untrusted=False):
285 def configitems(self, section, untrusted=False):
300 items = self._configitems(section, untrusted=untrusted, abort=True)
286 items = self._configitems(section, untrusted=untrusted, abort=True)
301 if self.debugflag and not untrusted and self.ucdata:
287 if self.debugflag and not untrusted and self.ucdata:
302 uitems = self._configitems(section, untrusted=True, abort=False)
288 uitems = self._configitems(section, untrusted=True, abort=False)
303 for k in util.sort(uitems):
289 for k in util.sort(uitems):
304 if uitems[k] != items.get(k):
290 if uitems[k] != items.get(k):
305 self.warn(_("Ignoring untrusted configuration option "
291 self.warn(_("Ignoring untrusted configuration option "
306 "%s.%s = %s\n") % (section, k, uitems[k]))
292 "%s.%s = %s\n") % (section, k, uitems[k]))
307 return util.sort(items.items())
293 return util.sort(items.items())
308
294
309 def walkconfig(self, untrusted=False):
295 def walkconfig(self, untrusted=False):
310 cdata = self._get_cdata(untrusted)
296 cdata = self._get_cdata(untrusted)
311 sections = cdata.sections()
297 sections = cdata.sections()
312 sections.sort()
298 sections.sort()
313 for section in sections:
299 for section in sections:
314 for name, value in self.configitems(section, untrusted):
300 for name, value in self.configitems(section, untrusted):
315 yield section, name, str(value).replace('\n', '\\n')
301 yield section, name, str(value).replace('\n', '\\n')
316
302
317 def username(self):
303 def username(self):
318 """Return default username to be used in commits.
304 """Return default username to be used in commits.
319
305
320 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
306 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
321 and stop searching if one of these is set.
307 and stop searching if one of these is set.
322 If not found and ui.askusername is True, ask the user, else use
308 If not found and ui.askusername is True, ask the user, else use
323 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
309 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
324 """
310 """
325 user = os.environ.get("HGUSER")
311 user = os.environ.get("HGUSER")
326 if user is None:
312 if user is None:
327 user = self.config("ui", "username")
313 user = self.config("ui", "username")
328 if user is None:
314 if user is None:
329 user = os.environ.get("EMAIL")
315 user = os.environ.get("EMAIL")
330 if user is None and self.configbool("ui", "askusername"):
316 if user is None and self.configbool("ui", "askusername"):
331 user = self.prompt(_("enter a commit username:"), default=None)
317 user = self.prompt(_("enter a commit username:"), default=None)
332 if user is None:
318 if user is None:
333 try:
319 try:
334 user = '%s@%s' % (util.getuser(), socket.getfqdn())
320 user = '%s@%s' % (util.getuser(), socket.getfqdn())
335 self.warn(_("No username found, using '%s' instead\n") % user)
321 self.warn(_("No username found, using '%s' instead\n") % user)
336 except KeyError:
322 except KeyError:
337 pass
323 pass
338 if not user:
324 if not user:
339 raise util.Abort(_("Please specify a username."))
325 raise util.Abort(_("Please specify a username."))
340 if "\n" in user:
326 if "\n" in user:
341 raise util.Abort(_("username %s contains a newline\n") % repr(user))
327 raise util.Abort(_("username %s contains a newline\n") % repr(user))
342 return user
328 return user
343
329
344 def shortuser(self, user):
330 def shortuser(self, user):
345 """Return a short representation of a user name or email address."""
331 """Return a short representation of a user name or email address."""
346 if not self.verbose: user = util.shortuser(user)
332 if not self.verbose: user = util.shortuser(user)
347 return user
333 return user
348
334
349 def expandpath(self, loc, default=None):
335 def expandpath(self, loc, default=None):
350 """Return repository location relative to cwd or from [paths]"""
336 """Return repository location relative to cwd or from [paths]"""
351 if "://" in loc or os.path.isdir(os.path.join(loc, '.hg')):
337 if "://" in loc or os.path.isdir(os.path.join(loc, '.hg')):
352 return loc
338 return loc
353
339
354 path = self.config("paths", loc)
340 path = self.config("paths", loc)
355 if not path and default is not None:
341 if not path and default is not None:
356 path = self.config("paths", default)
342 path = self.config("paths", default)
357 return path or loc
343 return path or loc
358
344
359 def pushbuffer(self):
345 def pushbuffer(self):
360 self.buffers.append([])
346 self.buffers.append([])
361
347
362 def popbuffer(self):
348 def popbuffer(self):
363 return "".join(self.buffers.pop())
349 return "".join(self.buffers.pop())
364
350
365 def write(self, *args):
351 def write(self, *args):
366 if self.buffers:
352 if self.buffers:
367 self.buffers[-1].extend([str(a) for a in args])
353 self.buffers[-1].extend([str(a) for a in args])
368 else:
354 else:
369 for a in args:
355 for a in args:
370 sys.stdout.write(str(a))
356 sys.stdout.write(str(a))
371
357
372 def write_err(self, *args):
358 def write_err(self, *args):
373 try:
359 try:
374 if not sys.stdout.closed: sys.stdout.flush()
360 if not sys.stdout.closed: sys.stdout.flush()
375 for a in args:
361 for a in args:
376 sys.stderr.write(str(a))
362 sys.stderr.write(str(a))
377 # stderr may be buffered under win32 when redirected to files,
363 # stderr may be buffered under win32 when redirected to files,
378 # including stdout.
364 # including stdout.
379 if not sys.stderr.closed: sys.stderr.flush()
365 if not sys.stderr.closed: sys.stderr.flush()
380 except IOError, inst:
366 except IOError, inst:
381 if inst.errno != errno.EPIPE:
367 if inst.errno != errno.EPIPE:
382 raise
368 raise
383
369
384 def flush(self):
370 def flush(self):
385 try: sys.stdout.flush()
371 try: sys.stdout.flush()
386 except: pass
372 except: pass
387 try: sys.stderr.flush()
373 try: sys.stderr.flush()
388 except: pass
374 except: pass
389
375
390 def _readline(self, prompt=''):
376 def _readline(self, prompt=''):
391 if self.isatty():
377 if self.isatty():
392 try:
378 try:
393 # magically add command line editing support, where
379 # magically add command line editing support, where
394 # available
380 # available
395 import readline
381 import readline
396 # force demandimport to really load the module
382 # force demandimport to really load the module
397 readline.read_history_file
383 readline.read_history_file
398 # windows sometimes raises something other than ImportError
384 # windows sometimes raises something other than ImportError
399 except Exception:
385 except Exception:
400 pass
386 pass
401 line = raw_input(prompt)
387 line = raw_input(prompt)
402 # When stdin is in binary mode on Windows, it can cause
388 # When stdin is in binary mode on Windows, it can cause
403 # raw_input() to emit an extra trailing carriage return
389 # raw_input() to emit an extra trailing carriage return
404 if os.linesep == '\r\n' and line and line[-1] == '\r':
390 if os.linesep == '\r\n' and line and line[-1] == '\r':
405 line = line[:-1]
391 line = line[:-1]
406 return line
392 return line
407
393
408 def prompt(self, msg, pat=None, default="y"):
394 def prompt(self, msg, pat=None, default="y"):
409 """Prompt user with msg, read response, and ensure it matches pat
395 """Prompt user with msg, read response, and ensure it matches pat
410
396
411 If not interactive -- the default is returned
397 If not interactive -- the default is returned
412 """
398 """
413 if not self.interactive:
399 if not self.interactive:
414 self.note(msg, ' ', default, "\n")
400 self.note(msg, ' ', default, "\n")
415 return default
401 return default
416 while True:
402 while True:
417 try:
403 try:
418 r = self._readline(msg + ' ')
404 r = self._readline(msg + ' ')
419 if not r:
405 if not r:
420 return default
406 return default
421 if not pat or re.match(pat, r):
407 if not pat or re.match(pat, r):
422 return r
408 return r
423 else:
409 else:
424 self.write(_("unrecognized response\n"))
410 self.write(_("unrecognized response\n"))
425 except EOFError:
411 except EOFError:
426 raise util.Abort(_('response expected'))
412 raise util.Abort(_('response expected'))
427
413
428 def getpass(self, prompt=None, default=None):
414 def getpass(self, prompt=None, default=None):
429 if not self.interactive: return default
415 if not self.interactive: return default
430 try:
416 try:
431 return getpass.getpass(prompt or _('password: '))
417 return getpass.getpass(prompt or _('password: '))
432 except EOFError:
418 except EOFError:
433 raise util.Abort(_('response expected'))
419 raise util.Abort(_('response expected'))
434 def status(self, *msg):
420 def status(self, *msg):
435 if not self.quiet: self.write(*msg)
421 if not self.quiet: self.write(*msg)
436 def warn(self, *msg):
422 def warn(self, *msg):
437 self.write_err(*msg)
423 self.write_err(*msg)
438 def note(self, *msg):
424 def note(self, *msg):
439 if self.verbose: self.write(*msg)
425 if self.verbose: self.write(*msg)
440 def debug(self, *msg):
426 def debug(self, *msg):
441 if self.debugflag: self.write(*msg)
427 if self.debugflag: self.write(*msg)
442 def edit(self, text, user):
428 def edit(self, text, user):
443 (fd, name) = tempfile.mkstemp(prefix="hg-editor-", suffix=".txt",
429 (fd, name) = tempfile.mkstemp(prefix="hg-editor-", suffix=".txt",
444 text=True)
430 text=True)
445 try:
431 try:
446 f = os.fdopen(fd, "w")
432 f = os.fdopen(fd, "w")
447 f.write(text)
433 f.write(text)
448 f.close()
434 f.close()
449
435
450 editor = self.geteditor()
436 editor = self.geteditor()
451
437
452 util.system("%s \"%s\"" % (editor, name),
438 util.system("%s \"%s\"" % (editor, name),
453 environ={'HGUSER': user},
439 environ={'HGUSER': user},
454 onerr=util.Abort, errprefix=_("edit failed"))
440 onerr=util.Abort, errprefix=_("edit failed"))
455
441
456 f = open(name)
442 f = open(name)
457 t = f.read()
443 t = f.read()
458 f.close()
444 f.close()
459 t = re.sub("(?m)^HG:.*\n", "", t)
445 t = re.sub("(?m)^HG:.*\n", "", t)
460 finally:
446 finally:
461 os.unlink(name)
447 os.unlink(name)
462
448
463 return t
449 return t
464
450
465 def print_exc(self):
451 def print_exc(self):
466 '''print exception traceback if traceback printing enabled.
452 '''print exception traceback if traceback printing enabled.
467 only to call in exception handler. returns true if traceback
453 only to call in exception handler. returns true if traceback
468 printed.'''
454 printed.'''
469 if self.traceback:
455 if self.traceback:
470 traceback.print_exc()
456 traceback.print_exc()
471 return self.traceback
457 return self.traceback
472
458
473 def geteditor(self):
459 def geteditor(self):
474 '''return editor to use'''
460 '''return editor to use'''
475 return (os.environ.get("HGEDITOR") or
461 return (os.environ.get("HGEDITOR") or
476 self.config("ui", "editor") or
462 self.config("ui", "editor") or
477 os.environ.get("VISUAL") or
463 os.environ.get("VISUAL") or
478 os.environ.get("EDITOR", "vi"))
464 os.environ.get("EDITOR", "vi"))
@@ -1,71 +1,68
1 #!/bin/sh
1 #!/bin/sh
2 # Tests if hgweb can run without touching sys.stdin, as is required
2 # Tests if hgweb can run without touching sys.stdin, as is required
3 # by the WSGI standard and strictly implemented by mod_wsgi.
3 # by the WSGI standard and strictly implemented by mod_wsgi.
4
4
5 mkdir repo
5 mkdir repo
6 cd repo
6 cd repo
7 hg init
7 hg init
8 echo foo > bar
8 echo foo > bar
9 hg add bar
9 hg add bar
10 hg commit -m "test" -d "0 0"
10 hg commit -m "test" -d "0 0"
11 hg tip
11 hg tip
12
12
13 cat > request.py <<EOF
13 cat > request.py <<EOF
14 from mercurial import dispatch
14 from mercurial import dispatch
15 from mercurial.hgweb.hgweb_mod import hgweb
15 from mercurial.hgweb.hgweb_mod import hgweb
16 from mercurial.ui import ui
16 from mercurial.ui import ui
17 from mercurial import hg
17 from mercurial import hg
18 from StringIO import StringIO
18 from StringIO import StringIO
19 import os, sys
19 import os, sys
20
20
21 class FileLike(object):
21 class FileLike(object):
22 def __init__(self, real):
22 def __init__(self, real):
23 self.real = real
23 self.real = real
24 def fileno(self):
24 def fileno(self):
25 print >> sys.__stdout__, 'FILENO'
25 print >> sys.__stdout__, 'FILENO'
26 return self.real.fileno()
26 return self.real.fileno()
27 def read(self):
27 def read(self):
28 print >> sys.__stdout__, 'READ'
28 print >> sys.__stdout__, 'READ'
29 return self.real.read()
29 return self.real.read()
30 def readline(self):
30 def readline(self):
31 print >> sys.__stdout__, 'READLINE'
31 print >> sys.__stdout__, 'READLINE'
32 return self.real.readline()
32 return self.real.readline()
33 def isatty(self):
34 print >> sys.__stdout__, 'ISATTY'
35 return False
36
33
37 sys.stdin = FileLike(sys.stdin)
34 sys.stdin = FileLike(sys.stdin)
38 errors = StringIO()
35 errors = StringIO()
39 input = StringIO()
36 input = StringIO()
40 output = StringIO()
37 output = StringIO()
41
38
42 def startrsp(headers, data):
39 def startrsp(headers, data):
43 print '---- HEADERS'
40 print '---- HEADERS'
44 print headers
41 print headers
45 print '---- DATA'
42 print '---- DATA'
46 print data
43 print data
47 return output.write
44 return output.write
48
45
49 env = {
46 env = {
50 'wsgi.version': (1, 0),
47 'wsgi.version': (1, 0),
51 'wsgi.url_scheme': 'http',
48 'wsgi.url_scheme': 'http',
52 'wsgi.errors': errors,
49 'wsgi.errors': errors,
53 'wsgi.input': input,
50 'wsgi.input': input,
54 'wsgi.multithread': False,
51 'wsgi.multithread': False,
55 'wsgi.multiprocess': False,
52 'wsgi.multiprocess': False,
56 'wsgi.run_once': False,
53 'wsgi.run_once': False,
57 'REQUEST_METHOD': 'GET',
54 'REQUEST_METHOD': 'GET',
58 'SCRIPT_NAME': '',
55 'SCRIPT_NAME': '',
59 'PATH_INFO': '',
56 'PATH_INFO': '',
60 'QUERY_STRING': '',
57 'QUERY_STRING': '',
61 'SERVER_NAME': '127.0.0.1',
58 'SERVER_NAME': '127.0.0.1',
62 'SERVER_PORT': os.environ['HGPORT'],
59 'SERVER_PORT': os.environ['HGPORT'],
63 'SERVER_PROTOCOL': 'HTTP/1.0'
60 'SERVER_PROTOCOL': 'HTTP/1.0'
64 }
61 }
65
62
66 hgweb('.')(env, startrsp)
63 hgweb('.')(env, startrsp)
67 print '---- ERRORS'
64 print '---- ERRORS'
68 print errors.getvalue()
65 print errors.getvalue()
69 EOF
66 EOF
70
67
71 python request.py
68 python request.py
@@ -1,211 +1,211
1 # Since it's not easy to write a test that portably deals
1 # Since it's not easy to write a test that portably deals
2 # with files from different users/groups, we cheat a bit by
2 # with files from different users/groups, we cheat a bit by
3 # monkey-patching some functions in the util module
3 # monkey-patching some functions in the util module
4
4
5 import os
5 import os
6 from mercurial import ui, util
6 from mercurial import ui, util
7
7
8 hgrc = os.environ['HGRCPATH']
8 hgrc = os.environ['HGRCPATH']
9 f = open(hgrc)
9 f = open(hgrc)
10 basehgrc = f.read()
10 basehgrc = f.read()
11 f.close()
11 f.close()
12
12
13 def testui(user='foo', group='bar', tusers=(), tgroups=(),
13 def testui(user='foo', group='bar', tusers=(), tgroups=(),
14 cuser='foo', cgroup='bar', debug=False, silent=False):
14 cuser='foo', cgroup='bar', debug=False, silent=False):
15 # user, group => owners of the file
15 # user, group => owners of the file
16 # tusers, tgroups => trusted users/groups
16 # tusers, tgroups => trusted users/groups
17 # cuser, cgroup => user/group of the current process
17 # cuser, cgroup => user/group of the current process
18
18
19 # write a global hgrc with the list of trusted users/groups and
19 # write a global hgrc with the list of trusted users/groups and
20 # some setting so that we can be sure it was read
20 # some setting so that we can be sure it was read
21 f = open(hgrc, 'w')
21 f = open(hgrc, 'w')
22 f.write(basehgrc)
22 f.write(basehgrc)
23 f.write('\n[paths]\n')
23 f.write('\n[paths]\n')
24 f.write('global = /some/path\n\n')
24 f.write('global = /some/path\n\n')
25
25
26 if tusers or tgroups:
26 if tusers or tgroups:
27 f.write('[trusted]\n')
27 f.write('[trusted]\n')
28 if tusers:
28 if tusers:
29 f.write('users = %s\n' % ', '.join(tusers))
29 f.write('users = %s\n' % ', '.join(tusers))
30 if tgroups:
30 if tgroups:
31 f.write('groups = %s\n' % ', '.join(tgroups))
31 f.write('groups = %s\n' % ', '.join(tgroups))
32 f.close()
32 f.close()
33
33
34 # override the functions that give names to uids and gids
34 # override the functions that give names to uids and gids
35 def username(uid=None):
35 def username(uid=None):
36 if uid is None:
36 if uid is None:
37 return cuser
37 return cuser
38 return user
38 return user
39 util.username = username
39 util.username = username
40
40
41 def groupname(gid=None):
41 def groupname(gid=None):
42 if gid is None:
42 if gid is None:
43 return 'bar'
43 return 'bar'
44 return group
44 return group
45 util.groupname = groupname
45 util.groupname = groupname
46
46
47 def isowner(fp, st=None):
47 def isowner(fp, st=None):
48 return user == cuser
48 return user == cuser
49 util.isowner = isowner
49 util.isowner = isowner
50
50
51 # try to read everything
51 # try to read everything
52 #print '# File belongs to user %s, group %s' % (user, group)
52 #print '# File belongs to user %s, group %s' % (user, group)
53 #print '# trusted users = %s; trusted groups = %s' % (tusers, tgroups)
53 #print '# trusted users = %s; trusted groups = %s' % (tusers, tgroups)
54 kind = ('different', 'same')
54 kind = ('different', 'same')
55 who = ('', 'user', 'group', 'user and the group')
55 who = ('', 'user', 'group', 'user and the group')
56 trusted = who[(user in tusers) + 2*(group in tgroups)]
56 trusted = who[(user in tusers) + 2*(group in tgroups)]
57 if trusted:
57 if trusted:
58 trusted = ', but we trust the ' + trusted
58 trusted = ', but we trust the ' + trusted
59 print '# %s user, %s group%s' % (kind[user == cuser], kind[group == cgroup],
59 print '# %s user, %s group%s' % (kind[user == cuser], kind[group == cgroup],
60 trusted)
60 trusted)
61
61
62 parentui = ui.ui()
62 parentui = ui.ui()
63 parentui.updateopts(debug=debug)
63 parentui.setconfig('ui', 'debug', str(bool(debug)))
64 u = ui.ui(parentui=parentui)
64 u = ui.ui(parentui=parentui)
65 u.readconfig('.hg/hgrc')
65 u.readconfig('.hg/hgrc')
66 if silent:
66 if silent:
67 return u
67 return u
68 print 'trusted'
68 print 'trusted'
69 for name, path in u.configitems('paths'):
69 for name, path in u.configitems('paths'):
70 print ' ', name, '=', path
70 print ' ', name, '=', path
71 print 'untrusted'
71 print 'untrusted'
72 for name, path in u.configitems('paths', untrusted=True):
72 for name, path in u.configitems('paths', untrusted=True):
73 print '.',
73 print '.',
74 u.config('paths', name) # warning with debug=True
74 u.config('paths', name) # warning with debug=True
75 print '.',
75 print '.',
76 u.config('paths', name, untrusted=True) # no warnings
76 u.config('paths', name, untrusted=True) # no warnings
77 print name, '=', path
77 print name, '=', path
78 print
78 print
79
79
80 return u
80 return u
81
81
82 os.mkdir('repo')
82 os.mkdir('repo')
83 os.chdir('repo')
83 os.chdir('repo')
84 os.mkdir('.hg')
84 os.mkdir('.hg')
85 f = open('.hg/hgrc', 'w')
85 f = open('.hg/hgrc', 'w')
86 f.write('[paths]\n')
86 f.write('[paths]\n')
87 f.write('local = /another/path\n\n')
87 f.write('local = /another/path\n\n')
88 f.write('interpolated = %(global)s%(local)s\n\n')
88 f.write('interpolated = %(global)s%(local)s\n\n')
89 f.close()
89 f.close()
90
90
91 #print '# Everything is run by user foo, group bar\n'
91 #print '# Everything is run by user foo, group bar\n'
92
92
93 # same user, same group
93 # same user, same group
94 testui()
94 testui()
95 # same user, different group
95 # same user, different group
96 testui(group='def')
96 testui(group='def')
97 # different user, same group
97 # different user, same group
98 testui(user='abc')
98 testui(user='abc')
99 # ... but we trust the group
99 # ... but we trust the group
100 testui(user='abc', tgroups=['bar'])
100 testui(user='abc', tgroups=['bar'])
101 # different user, different group
101 # different user, different group
102 testui(user='abc', group='def')
102 testui(user='abc', group='def')
103 # ... but we trust the user
103 # ... but we trust the user
104 testui(user='abc', group='def', tusers=['abc'])
104 testui(user='abc', group='def', tusers=['abc'])
105 # ... but we trust the group
105 # ... but we trust the group
106 testui(user='abc', group='def', tgroups=['def'])
106 testui(user='abc', group='def', tgroups=['def'])
107 # ... but we trust the user and the group
107 # ... but we trust the user and the group
108 testui(user='abc', group='def', tusers=['abc'], tgroups=['def'])
108 testui(user='abc', group='def', tusers=['abc'], tgroups=['def'])
109 # ... but we trust all users
109 # ... but we trust all users
110 print '# we trust all users'
110 print '# we trust all users'
111 testui(user='abc', group='def', tusers=['*'])
111 testui(user='abc', group='def', tusers=['*'])
112 # ... but we trust all groups
112 # ... but we trust all groups
113 print '# we trust all groups'
113 print '# we trust all groups'
114 testui(user='abc', group='def', tgroups=['*'])
114 testui(user='abc', group='def', tgroups=['*'])
115 # ... but we trust the whole universe
115 # ... but we trust the whole universe
116 print '# we trust all users and groups'
116 print '# we trust all users and groups'
117 testui(user='abc', group='def', tusers=['*'], tgroups=['*'])
117 testui(user='abc', group='def', tusers=['*'], tgroups=['*'])
118 # ... check that users and groups are in different namespaces
118 # ... check that users and groups are in different namespaces
119 print "# we don't get confused by users and groups with the same name"
119 print "# we don't get confused by users and groups with the same name"
120 testui(user='abc', group='def', tusers=['def'], tgroups=['abc'])
120 testui(user='abc', group='def', tusers=['def'], tgroups=['abc'])
121 # ... lists of user names work
121 # ... lists of user names work
122 print "# list of user names"
122 print "# list of user names"
123 testui(user='abc', group='def', tusers=['foo', 'xyz', 'abc', 'bleh'],
123 testui(user='abc', group='def', tusers=['foo', 'xyz', 'abc', 'bleh'],
124 tgroups=['bar', 'baz', 'qux'])
124 tgroups=['bar', 'baz', 'qux'])
125 # ... lists of group names work
125 # ... lists of group names work
126 print "# list of group names"
126 print "# list of group names"
127 testui(user='abc', group='def', tusers=['foo', 'xyz', 'bleh'],
127 testui(user='abc', group='def', tusers=['foo', 'xyz', 'bleh'],
128 tgroups=['bar', 'def', 'baz', 'qux'])
128 tgroups=['bar', 'def', 'baz', 'qux'])
129
129
130 print "# Can't figure out the name of the user running this process"
130 print "# Can't figure out the name of the user running this process"
131 testui(user='abc', group='def', cuser=None)
131 testui(user='abc', group='def', cuser=None)
132
132
133 print "# prints debug warnings"
133 print "# prints debug warnings"
134 u = testui(user='abc', group='def', cuser='foo', debug=True)
134 u = testui(user='abc', group='def', cuser='foo', debug=True)
135
135
136 print "# ui.readsections"
136 print "# ui.readsections"
137 filename = 'foobar'
137 filename = 'foobar'
138 f = open(filename, 'w')
138 f = open(filename, 'w')
139 f.write('[foobar]\n')
139 f.write('[foobar]\n')
140 f.write('baz = quux\n')
140 f.write('baz = quux\n')
141 f.close()
141 f.close()
142 u.readsections(filename, 'foobar')
142 u.readsections(filename, 'foobar')
143 print u.config('foobar', 'baz')
143 print u.config('foobar', 'baz')
144
144
145 print
145 print
146 print "# read trusted, untrusted, new ui, trusted"
146 print "# read trusted, untrusted, new ui, trusted"
147 u = ui.ui()
147 u = ui.ui()
148 u.updateopts(debug=True)
148 u.setconfig('ui', 'debug', 'on')
149 u.readconfig(filename)
149 u.readconfig(filename)
150 u2 = ui.ui(parentui=u)
150 u2 = ui.ui(parentui=u)
151 def username(uid=None):
151 def username(uid=None):
152 return 'foo'
152 return 'foo'
153 util.username = username
153 util.username = username
154 u2.readconfig('.hg/hgrc')
154 u2.readconfig('.hg/hgrc')
155 print 'trusted:'
155 print 'trusted:'
156 print u2.config('foobar', 'baz')
156 print u2.config('foobar', 'baz')
157 print u2.config('paths', 'interpolated')
157 print u2.config('paths', 'interpolated')
158 print 'untrusted:'
158 print 'untrusted:'
159 print u2.config('foobar', 'baz', untrusted=True)
159 print u2.config('foobar', 'baz', untrusted=True)
160 print u2.config('paths', 'interpolated', untrusted=True)
160 print u2.config('paths', 'interpolated', untrusted=True)
161
161
162 print
162 print
163 print "# error handling"
163 print "# error handling"
164
164
165 def assertraises(f, exc=util.Abort):
165 def assertraises(f, exc=util.Abort):
166 try:
166 try:
167 f()
167 f()
168 except exc, inst:
168 except exc, inst:
169 print 'raised', inst.__class__.__name__
169 print 'raised', inst.__class__.__name__
170 else:
170 else:
171 print 'no exception?!'
171 print 'no exception?!'
172
172
173 print "# file doesn't exist"
173 print "# file doesn't exist"
174 os.unlink('.hg/hgrc')
174 os.unlink('.hg/hgrc')
175 assert not os.path.exists('.hg/hgrc')
175 assert not os.path.exists('.hg/hgrc')
176 testui(debug=True, silent=True)
176 testui(debug=True, silent=True)
177 testui(user='abc', group='def', debug=True, silent=True)
177 testui(user='abc', group='def', debug=True, silent=True)
178
178
179 print
179 print
180 print "# parse error"
180 print "# parse error"
181 f = open('.hg/hgrc', 'w')
181 f = open('.hg/hgrc', 'w')
182 f.write('foo = bar')
182 f.write('foo = bar')
183 f.close()
183 f.close()
184 testui(user='abc', group='def', silent=True)
184 testui(user='abc', group='def', silent=True)
185 assertraises(lambda: testui(debug=True, silent=True))
185 assertraises(lambda: testui(debug=True, silent=True))
186
186
187 print
187 print
188 print "# interpolation error"
188 print "# interpolation error"
189 f = open('.hg/hgrc', 'w')
189 f = open('.hg/hgrc', 'w')
190 f.write('[foo]\n')
190 f.write('[foo]\n')
191 f.write('bar = %(')
191 f.write('bar = %(')
192 f.close()
192 f.close()
193 u = testui(debug=True, silent=True)
193 u = testui(debug=True, silent=True)
194 print '# regular config:'
194 print '# regular config:'
195 print ' trusted',
195 print ' trusted',
196 assertraises(lambda: u.config('foo', 'bar'))
196 assertraises(lambda: u.config('foo', 'bar'))
197 print 'untrusted',
197 print 'untrusted',
198 assertraises(lambda: u.config('foo', 'bar', untrusted=True))
198 assertraises(lambda: u.config('foo', 'bar', untrusted=True))
199
199
200 u = testui(user='abc', group='def', debug=True, silent=True)
200 u = testui(user='abc', group='def', debug=True, silent=True)
201 print ' trusted ',
201 print ' trusted ',
202 print u.config('foo', 'bar')
202 print u.config('foo', 'bar')
203 print 'untrusted',
203 print 'untrusted',
204 assertraises(lambda: u.config('foo', 'bar', untrusted=True))
204 assertraises(lambda: u.config('foo', 'bar', untrusted=True))
205
205
206 print '# configitems:'
206 print '# configitems:'
207 print ' trusted ',
207 print ' trusted ',
208 print u.configitems('foo')
208 print u.configitems('foo')
209 print 'untrusted',
209 print 'untrusted',
210 assertraises(lambda: u.configitems('foo', untrusted=True))
210 assertraises(lambda: u.configitems('foo', untrusted=True))
211
211
@@ -1,46 +1,49
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2
2
3 import os
3 import os
4 from mercurial import ui
4 from mercurial import ui
5
5
6 hgrc = os.environ['HGRCPATH']
6 hgrc = os.environ['HGRCPATH']
7 f = open(hgrc)
7 f = open(hgrc)
8 basehgrc = f.read()
8 basehgrc = f.read()
9 f.close()
9 f.close()
10
10
11 print ' hgrc settings command line options final result '
11 print ' hgrc settings command line options final result '
12 print ' quiet verbo debug quiet verbo debug quiet verbo debug'
12 print ' quiet verbo debug quiet verbo debug quiet verbo debug'
13
13
14 for i in xrange(64):
14 for i in xrange(64):
15 hgrc_quiet = bool(i & 1<<0)
15 hgrc_quiet = bool(i & 1<<0)
16 hgrc_verbose = bool(i & 1<<1)
16 hgrc_verbose = bool(i & 1<<1)
17 hgrc_debug = bool(i & 1<<2)
17 hgrc_debug = bool(i & 1<<2)
18 cmd_quiet = bool(i & 1<<3)
18 cmd_quiet = bool(i & 1<<3)
19 cmd_verbose = bool(i & 1<<4)
19 cmd_verbose = bool(i & 1<<4)
20 cmd_debug = bool(i & 1<<5)
20 cmd_debug = bool(i & 1<<5)
21
21
22 f = open(hgrc, 'w')
22 f = open(hgrc, 'w')
23 f.write(basehgrc)
23 f.write(basehgrc)
24 f.write('\n[ui]\n')
24 f.write('\n[ui]\n')
25 if hgrc_quiet:
25 if hgrc_quiet:
26 f.write('quiet = True\n')
26 f.write('quiet = True\n')
27 if hgrc_verbose:
27 if hgrc_verbose:
28 f.write('verbose = True\n')
28 f.write('verbose = True\n')
29 if hgrc_debug:
29 if hgrc_debug:
30 f.write('debug = True\n')
30 f.write('debug = True\n')
31 f.close()
31 f.close()
32
32
33 u = ui.ui()
33 u = ui.ui()
34 u.updateopts(quiet=cmd_quiet, verbose=cmd_verbose, debug=cmd_debug)
34 if cmd_quiet or cmd_debug or cmd_verbose:
35 u.setconfig('ui', 'quiet', str(bool(cmd_quiet)))
36 u.setconfig('ui', 'verbose', str(bool(cmd_verbose)))
37 u.setconfig('ui', 'debug', str(bool(cmd_debug)))
35
38
36 check = ''
39 check = ''
37 if u.debugflag:
40 if u.debugflag:
38 if not u.verbose or u.quiet:
41 if not u.verbose or u.quiet:
39 check = ' *'
42 check = ' *'
40 elif u.verbose and u.quiet:
43 elif u.verbose and u.quiet:
41 check = ' +'
44 check = ' +'
42
45
43 print ('%2d %5s %5s %5s %5s %5s %5s -> %5s %5s %5s%s'
46 print ('%2d %5s %5s %5s %5s %5s %5s -> %5s %5s %5s%s'
44 % (i, hgrc_quiet, hgrc_verbose, hgrc_debug,
47 % (i, hgrc_quiet, hgrc_verbose, hgrc_debug,
45 cmd_quiet, cmd_verbose, cmd_debug,
48 cmd_quiet, cmd_verbose, cmd_debug,
46 u.quiet, u.verbose, u.debugflag, check))
49 u.quiet, u.verbose, u.debugflag, check))
General Comments 0
You need to be logged in to leave comments. Login now