##// END OF EJS Templates
hgwebdir: read --webdir-conf as actual configuration to ui (issue1586)...
Alexander Solovyov -
r8345:dcebff8a default
parent child Browse files
Show More
@@ -1,318 +1,317 b''
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 of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2, incorporated herein by reference.
7 # GNU General Public License version 2, 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
11 from mercurial import ui, hg, util, templater, templatefilters
12 from mercurial import config, error, encoding
12 from mercurial import error, encoding
13 from common import ErrorResponse, get_mtime, staticfile, paritygen,\
13 from common import ErrorResponse, get_mtime, staticfile, paritygen,\
14 get_contact, HTTP_OK, HTTP_NOT_FOUND, HTTP_SERVER_ERROR
14 get_contact, HTTP_OK, HTTP_NOT_FOUND, HTTP_SERVER_ERROR
15 from hgweb_mod import hgweb
15 from hgweb_mod import hgweb
16 from request import wsgirequest
16 from request import wsgirequest
17 import webutil
17 import webutil
18
18
19 def cleannames(items):
19 def cleannames(items):
20 return [(util.pconvert(name).strip('/'), path) for name, path in items]
20 return [(util.pconvert(name).strip('/'), path) for name, path in items]
21
21
22 class hgwebdir(object):
22 class hgwebdir(object):
23
23
24 def __init__(self, conf, baseui=None):
24 def __init__(self, conf, baseui=None):
25
25
26 if baseui:
26 if baseui:
27 self.ui = baseui.copy()
27 self.ui = baseui.copy()
28 else:
28 else:
29 self.ui = ui.ui()
29 self.ui = ui.ui()
30 self.ui.setconfig('ui', 'report_untrusted', 'off')
30 self.ui.setconfig('ui', 'report_untrusted', 'off')
31 self.ui.setconfig('ui', 'interactive', 'off')
31 self.ui.setconfig('ui', 'interactive', 'off')
32
32
33 self.motd = None
34 self.style = 'paper'
35 self.stripecount = 1
36 self.repos_sorted = ('name', False)
33 self.repos_sorted = ('name', False)
37 self._baseurl = None
38
34
39 if isinstance(conf, (list, tuple)):
35 if isinstance(conf, (list, tuple)):
40 self.repos = cleannames(conf)
36 self.repos = cleannames(conf)
41 self.repos_sorted = ('', False)
37 self.repos_sorted = ('', False)
42 elif isinstance(conf, dict):
38 elif isinstance(conf, dict):
43 self.repos = sorted(cleannames(conf.items()))
39 self.repos = sorted(cleannames(conf.items()))
44 else:
40 else:
45 if isinstance(conf, config.config):
41 self.ui.readconfig(conf, remap={'paths': 'hgweb-paths'})
46 cp = conf
47 else:
48 cp = config.config()
49 cp.read(conf)
50 self.repos = []
42 self.repos = []
51 self.motd = cp.get('web', 'motd')
43
52 self.style = cp.get('web', 'style', 'paper')
44 self.motd = self.ui.config('web', 'motd')
53 self.stripecount = cp.get('web', 'stripes', 1)
45 self.style = self.ui.config('web', 'style', 'paper')
54 self._baseurl = cp.get('web', 'baseurl')
46 self.stripecount = self.ui.config('web', 'stripes', 1)
55 if 'paths' in cp:
47 if self.stripecount:
56 paths = cleannames(cp.items('paths'))
48 self.stripecount = int(self.stripecount)
57 for prefix, root in paths:
49 self._baseurl = self.ui.config('web', 'baseurl')
58 roothead, roottail = os.path.split(root)
50
59 # "foo = /bar/*" makes every subrepo of /bar/ to be
51 if self.repos:
60 # mounted as foo/subrepo
52 return
61 # and "foo = /bar/**" does even recurse inside the
53
62 # subdirectories, remember to use it without working dir.
54 for prefix, root in cleannames(self.ui.configitems('hgweb-paths')):
63 try:
55 roothead, roottail = os.path.split(root)
64 recurse = {'*': False, '**': True}[roottail]
56 # "foo = /bar/*" makes every subrepo of /bar/ to be
65 except KeyError:
57 # mounted as foo/subrepo
66 self.repos.append((prefix, root))
58 # and "foo = /bar/**" also recurses into the subdirectories,
67 continue
59 # remember to use it without working dir.
68 roothead = os.path.normpath(roothead)
60 try:
69 for path in util.walkrepos(roothead, followsym=True,
61 recurse = {'*': False, '**': True}[roottail]
70 recurse=recurse):
62 except KeyError:
71 path = os.path.normpath(path)
63 self.repos.append((prefix, root))
72 name = util.pconvert(path[len(roothead):]).strip('/')
64 continue
73 if prefix:
65 roothead = os.path.normpath(roothead)
74 name = prefix + '/' + name
66 for path in util.walkrepos(roothead, followsym=True,
75 self.repos.append((name, path))
67 recurse=recurse):
76 for prefix, root in cp.items('collections'):
68 path = os.path.normpath(path)
77 for path in util.walkrepos(root, followsym=True):
69 name = util.pconvert(path[len(roothead):]).strip('/')
78 repo = os.path.normpath(path)
70 if prefix:
79 name = repo
71 name = prefix + '/' + name
80 if name.startswith(prefix):
72 self.repos.append((name, path))
81 name = name[len(prefix):]
73
82 self.repos.append((name.lstrip(os.sep), repo))
74 for prefix, root in self.ui.configitems('collections'):
83 self.repos.sort()
75 for path in util.walkrepos(root, followsym=True):
76 repo = os.path.normpath(path)
77 name = repo
78 if name.startswith(prefix):
79 name = name[len(prefix):]
80 self.repos.append((name.lstrip(os.sep), repo))
81
82 self.repos.sort()
84
83
85 def run(self):
84 def run(self):
86 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
85 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
87 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
86 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
88 import mercurial.hgweb.wsgicgi as wsgicgi
87 import mercurial.hgweb.wsgicgi as wsgicgi
89 wsgicgi.launch(self)
88 wsgicgi.launch(self)
90
89
91 def __call__(self, env, respond):
90 def __call__(self, env, respond):
92 req = wsgirequest(env, respond)
91 req = wsgirequest(env, respond)
93 return self.run_wsgi(req)
92 return self.run_wsgi(req)
94
93
95 def read_allowed(self, ui, req):
94 def read_allowed(self, ui, req):
96 """Check allow_read and deny_read config options of a repo's ui object
95 """Check allow_read and deny_read config options of a repo's ui object
97 to determine user permissions. By default, with neither option set (or
96 to determine user permissions. By default, with neither option set (or
98 both empty), allow all users to read the repo. There are two ways a
97 both empty), allow all users to read the repo. There are two ways a
99 user can be denied read access: (1) deny_read is not empty, and the
98 user can be denied read access: (1) deny_read is not empty, and the
100 user is unauthenticated or deny_read contains user (or *), and (2)
99 user is unauthenticated or deny_read contains user (or *), and (2)
101 allow_read is not empty and the user is not in allow_read. Return True
100 allow_read is not empty and the user is not in allow_read. Return True
102 if user is allowed to read the repo, else return False."""
101 if user is allowed to read the repo, else return False."""
103
102
104 user = req.env.get('REMOTE_USER')
103 user = req.env.get('REMOTE_USER')
105
104
106 deny_read = ui.configlist('web', 'deny_read', untrusted=True)
105 deny_read = ui.configlist('web', 'deny_read', untrusted=True)
107 if deny_read and (not user or deny_read == ['*'] or user in deny_read):
106 if deny_read and (not user or deny_read == ['*'] or user in deny_read):
108 return False
107 return False
109
108
110 allow_read = ui.configlist('web', 'allow_read', untrusted=True)
109 allow_read = ui.configlist('web', 'allow_read', untrusted=True)
111 # by default, allow reading if no allow_read option has been set
110 # by default, allow reading if no allow_read option has been set
112 if (not allow_read) or (allow_read == ['*']) or (user in allow_read):
111 if (not allow_read) or (allow_read == ['*']) or (user in allow_read):
113 return True
112 return True
114
113
115 return False
114 return False
116
115
117 def run_wsgi(self, req):
116 def run_wsgi(self, req):
118
117
119 try:
118 try:
120 try:
119 try:
121
120
122 virtual = req.env.get("PATH_INFO", "").strip('/')
121 virtual = req.env.get("PATH_INFO", "").strip('/')
123 tmpl = self.templater(req)
122 tmpl = self.templater(req)
124 ctype = tmpl('mimetype', encoding=encoding.encoding)
123 ctype = tmpl('mimetype', encoding=encoding.encoding)
125 ctype = templater.stringify(ctype)
124 ctype = templater.stringify(ctype)
126
125
127 # a static file
126 # a static file
128 if virtual.startswith('static/') or 'static' in req.form:
127 if virtual.startswith('static/') or 'static' in req.form:
129 if virtual.startswith('static/'):
128 if virtual.startswith('static/'):
130 fname = virtual[7:]
129 fname = virtual[7:]
131 else:
130 else:
132 fname = req.form['static'][0]
131 fname = req.form['static'][0]
133 static = templater.templatepath('static')
132 static = templater.templatepath('static')
134 return (staticfile(static, fname, req),)
133 return (staticfile(static, fname, req),)
135
134
136 # top-level index
135 # top-level index
137 elif not virtual:
136 elif not virtual:
138 req.respond(HTTP_OK, ctype)
137 req.respond(HTTP_OK, ctype)
139 return self.makeindex(req, tmpl)
138 return self.makeindex(req, tmpl)
140
139
141 # nested indexes and hgwebs
140 # nested indexes and hgwebs
142
141
143 repos = dict(self.repos)
142 repos = dict(self.repos)
144 while virtual:
143 while virtual:
145 real = repos.get(virtual)
144 real = repos.get(virtual)
146 if real:
145 if real:
147 req.env['REPO_NAME'] = virtual
146 req.env['REPO_NAME'] = virtual
148 try:
147 try:
149 repo = hg.repository(self.ui, real)
148 repo = hg.repository(self.ui, real)
150 return hgweb(repo).run_wsgi(req)
149 return hgweb(repo).run_wsgi(req)
151 except IOError, inst:
150 except IOError, inst:
152 msg = inst.strerror
151 msg = inst.strerror
153 raise ErrorResponse(HTTP_SERVER_ERROR, msg)
152 raise ErrorResponse(HTTP_SERVER_ERROR, msg)
154 except error.RepoError, inst:
153 except error.RepoError, inst:
155 raise ErrorResponse(HTTP_SERVER_ERROR, str(inst))
154 raise ErrorResponse(HTTP_SERVER_ERROR, str(inst))
156
155
157 # browse subdirectories
156 # browse subdirectories
158 subdir = virtual + '/'
157 subdir = virtual + '/'
159 if [r for r in repos if r.startswith(subdir)]:
158 if [r for r in repos if r.startswith(subdir)]:
160 req.respond(HTTP_OK, ctype)
159 req.respond(HTTP_OK, ctype)
161 return self.makeindex(req, tmpl, subdir)
160 return self.makeindex(req, tmpl, subdir)
162
161
163 up = virtual.rfind('/')
162 up = virtual.rfind('/')
164 if up < 0:
163 if up < 0:
165 break
164 break
166 virtual = virtual[:up]
165 virtual = virtual[:up]
167
166
168 # prefixes not found
167 # prefixes not found
169 req.respond(HTTP_NOT_FOUND, ctype)
168 req.respond(HTTP_NOT_FOUND, ctype)
170 return tmpl("notfound", repo=virtual)
169 return tmpl("notfound", repo=virtual)
171
170
172 except ErrorResponse, err:
171 except ErrorResponse, err:
173 req.respond(err, ctype)
172 req.respond(err, ctype)
174 return tmpl('error', error=err.message or '')
173 return tmpl('error', error=err.message or '')
175 finally:
174 finally:
176 tmpl = None
175 tmpl = None
177
176
178 def makeindex(self, req, tmpl, subdir=""):
177 def makeindex(self, req, tmpl, subdir=""):
179
178
180 def archivelist(ui, nodeid, url):
179 def archivelist(ui, nodeid, url):
181 allowed = ui.configlist("web", "allow_archive", untrusted=True)
180 allowed = ui.configlist("web", "allow_archive", untrusted=True)
182 for i in [('zip', '.zip'), ('gz', '.tar.gz'), ('bz2', '.tar.bz2')]:
181 for i in [('zip', '.zip'), ('gz', '.tar.gz'), ('bz2', '.tar.bz2')]:
183 if i[0] in allowed or ui.configbool("web", "allow" + i[0],
182 if i[0] in allowed or ui.configbool("web", "allow" + i[0],
184 untrusted=True):
183 untrusted=True):
185 yield {"type" : i[0], "extension": i[1],
184 yield {"type" : i[0], "extension": i[1],
186 "node": nodeid, "url": url}
185 "node": nodeid, "url": url}
187
186
188 def entries(sortcolumn="", descending=False, subdir="", **map):
187 def entries(sortcolumn="", descending=False, subdir="", **map):
189 rows = []
188 rows = []
190 parity = paritygen(self.stripecount)
189 parity = paritygen(self.stripecount)
191 for name, path in self.repos:
190 for name, path in self.repos:
192 if not name.startswith(subdir):
191 if not name.startswith(subdir):
193 continue
192 continue
194 name = name[len(subdir):]
193 name = name[len(subdir):]
195
194
196 u = self.ui.copy()
195 u = self.ui.copy()
197 try:
196 try:
198 u.readconfig(os.path.join(path, '.hg', 'hgrc'))
197 u.readconfig(os.path.join(path, '.hg', 'hgrc'))
199 except Exception, e:
198 except Exception, e:
200 u.warn(_('error reading %s/.hg/hgrc: %s\n') % (path, e))
199 u.warn(_('error reading %s/.hg/hgrc: %s\n') % (path, e))
201 continue
200 continue
202 def get(section, name, default=None):
201 def get(section, name, default=None):
203 return u.config(section, name, default, untrusted=True)
202 return u.config(section, name, default, untrusted=True)
204
203
205 if u.configbool("web", "hidden", untrusted=True):
204 if u.configbool("web", "hidden", untrusted=True):
206 continue
205 continue
207
206
208 if not self.read_allowed(u, req):
207 if not self.read_allowed(u, req):
209 continue
208 continue
210
209
211 parts = [name]
210 parts = [name]
212 if 'PATH_INFO' in req.env:
211 if 'PATH_INFO' in req.env:
213 parts.insert(0, req.env['PATH_INFO'].rstrip('/'))
212 parts.insert(0, req.env['PATH_INFO'].rstrip('/'))
214 if req.env['SCRIPT_NAME']:
213 if req.env['SCRIPT_NAME']:
215 parts.insert(0, req.env['SCRIPT_NAME'])
214 parts.insert(0, req.env['SCRIPT_NAME'])
216 url = ('/'.join(parts).replace("//", "/")) + '/'
215 url = ('/'.join(parts).replace("//", "/")) + '/'
217
216
218 # update time with local timezone
217 # update time with local timezone
219 try:
218 try:
220 d = (get_mtime(path), util.makedate()[1])
219 d = (get_mtime(path), util.makedate()[1])
221 except OSError:
220 except OSError:
222 continue
221 continue
223
222
224 contact = get_contact(get)
223 contact = get_contact(get)
225 description = get("web", "description", "")
224 description = get("web", "description", "")
226 name = get("web", "name", name)
225 name = get("web", "name", name)
227 row = dict(contact=contact or "unknown",
226 row = dict(contact=contact or "unknown",
228 contact_sort=contact.upper() or "unknown",
227 contact_sort=contact.upper() or "unknown",
229 name=name,
228 name=name,
230 name_sort=name,
229 name_sort=name,
231 url=url,
230 url=url,
232 description=description or "unknown",
231 description=description or "unknown",
233 description_sort=description.upper() or "unknown",
232 description_sort=description.upper() or "unknown",
234 lastchange=d,
233 lastchange=d,
235 lastchange_sort=d[1]-d[0],
234 lastchange_sort=d[1]-d[0],
236 archives=archivelist(u, "tip", url))
235 archives=archivelist(u, "tip", url))
237 if (not sortcolumn
236 if (not sortcolumn
238 or (sortcolumn, descending) == self.repos_sorted):
237 or (sortcolumn, descending) == self.repos_sorted):
239 # fast path for unsorted output
238 # fast path for unsorted output
240 row['parity'] = parity.next()
239 row['parity'] = parity.next()
241 yield row
240 yield row
242 else:
241 else:
243 rows.append((row["%s_sort" % sortcolumn], row))
242 rows.append((row["%s_sort" % sortcolumn], row))
244 if rows:
243 if rows:
245 rows.sort()
244 rows.sort()
246 if descending:
245 if descending:
247 rows.reverse()
246 rows.reverse()
248 for key, row in rows:
247 for key, row in rows:
249 row['parity'] = parity.next()
248 row['parity'] = parity.next()
250 yield row
249 yield row
251
250
252 sortable = ["name", "description", "contact", "lastchange"]
251 sortable = ["name", "description", "contact", "lastchange"]
253 sortcolumn, descending = self.repos_sorted
252 sortcolumn, descending = self.repos_sorted
254 if 'sort' in req.form:
253 if 'sort' in req.form:
255 sortcolumn = req.form['sort'][0]
254 sortcolumn = req.form['sort'][0]
256 descending = sortcolumn.startswith('-')
255 descending = sortcolumn.startswith('-')
257 if descending:
256 if descending:
258 sortcolumn = sortcolumn[1:]
257 sortcolumn = sortcolumn[1:]
259 if sortcolumn not in sortable:
258 if sortcolumn not in sortable:
260 sortcolumn = ""
259 sortcolumn = ""
261
260
262 sort = [("sort_%s" % column,
261 sort = [("sort_%s" % column,
263 "%s%s" % ((not descending and column == sortcolumn)
262 "%s%s" % ((not descending and column == sortcolumn)
264 and "-" or "", column))
263 and "-" or "", column))
265 for column in sortable]
264 for column in sortable]
266
265
267 if self._baseurl is not None:
266 if self._baseurl is not None:
268 req.env['SCRIPT_NAME'] = self._baseurl
267 req.env['SCRIPT_NAME'] = self._baseurl
269
268
270 return tmpl("index", entries=entries, subdir=subdir,
269 return tmpl("index", entries=entries, subdir=subdir,
271 sortcolumn=sortcolumn, descending=descending,
270 sortcolumn=sortcolumn, descending=descending,
272 **dict(sort))
271 **dict(sort))
273
272
274 def templater(self, req):
273 def templater(self, req):
275
274
276 def header(**map):
275 def header(**map):
277 yield tmpl('header', encoding=encoding.encoding, **map)
276 yield tmpl('header', encoding=encoding.encoding, **map)
278
277
279 def footer(**map):
278 def footer(**map):
280 yield tmpl("footer", **map)
279 yield tmpl("footer", **map)
281
280
282 def motd(**map):
281 def motd(**map):
283 if self.motd is not None:
282 if self.motd is not None:
284 yield self.motd
283 yield self.motd
285 else:
284 else:
286 yield config('web', 'motd', '')
285 yield config('web', 'motd', '')
287
286
288 def config(section, name, default=None, untrusted=True):
287 def config(section, name, default=None, untrusted=True):
289 return self.ui.config(section, name, default, untrusted)
288 return self.ui.config(section, name, default, untrusted)
290
289
291 if self._baseurl is not None:
290 if self._baseurl is not None:
292 req.env['SCRIPT_NAME'] = self._baseurl
291 req.env['SCRIPT_NAME'] = self._baseurl
293
292
294 url = req.env.get('SCRIPT_NAME', '')
293 url = req.env.get('SCRIPT_NAME', '')
295 if not url.endswith('/'):
294 if not url.endswith('/'):
296 url += '/'
295 url += '/'
297
296
298 vars = {}
297 vars = {}
299 style = self.style
298 style = self.style
300 if 'style' in req.form:
299 if 'style' in req.form:
301 vars['style'] = style = req.form['style'][0]
300 vars['style'] = style = req.form['style'][0]
302 start = url[-1] == '?' and '&' or '?'
301 start = url[-1] == '?' and '&' or '?'
303 sessionvars = webutil.sessionvars(vars, start)
302 sessionvars = webutil.sessionvars(vars, start)
304
303
305 staticurl = config('web', 'staticurl') or url + 'static/'
304 staticurl = config('web', 'staticurl') or url + 'static/'
306 if not staticurl.endswith('/'):
305 if not staticurl.endswith('/'):
307 staticurl += '/'
306 staticurl += '/'
308
307
309 style = 'style' in req.form and req.form['style'][0] or self.style
308 style = 'style' in req.form and req.form['style'][0] or self.style
310 mapfile = templater.stylemap(style)
309 mapfile = templater.stylemap(style)
311 tmpl = templater.templater(mapfile, templatefilters.filters,
310 tmpl = templater.templater(mapfile, templatefilters.filters,
312 defaults={"header": header,
311 defaults={"header": header,
313 "footer": footer,
312 "footer": footer,
314 "motd": motd,
313 "motd": motd,
315 "url": url,
314 "url": url,
316 "staticurl": staticurl,
315 "staticurl": staticurl,
317 "sessionvars": sessionvars})
316 "sessionvars": sessionvars})
318 return tmpl
317 return tmpl
@@ -1,346 +1,346 b''
1 # ui.py - user interface bits for mercurial
1 # ui.py - user interface bits for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2, incorporated herein by reference.
6 # GNU General Public License version 2, incorporated herein by reference.
7
7
8 from i18n import _
8 from i18n import _
9 import errno, getpass, os, re, socket, sys, tempfile, traceback
9 import errno, getpass, os, re, socket, sys, tempfile, traceback
10 import config, util, error
10 import config, util, error
11
11
12 _booleans = {'1': True, 'yes': True, 'true': True, 'on': True,
12 _booleans = {'1': True, 'yes': True, 'true': True, 'on': True,
13 '0': False, 'no': False, 'false': False, 'off': False}
13 '0': False, 'no': False, 'false': False, 'off': False}
14
14
15 class ui(object):
15 class ui(object):
16 def __init__(self, src=None):
16 def __init__(self, src=None):
17 self._buffers = []
17 self._buffers = []
18 self.quiet = self.verbose = self.debugflag = self._traceback = False
18 self.quiet = self.verbose = self.debugflag = self._traceback = False
19 self._reportuntrusted = True
19 self._reportuntrusted = True
20 self._ocfg = config.config() # overlay
20 self._ocfg = config.config() # overlay
21 self._tcfg = config.config() # trusted
21 self._tcfg = config.config() # trusted
22 self._ucfg = config.config() # untrusted
22 self._ucfg = config.config() # untrusted
23 self._trustusers = {}
23 self._trustusers = {}
24 self._trustgroups = {}
24 self._trustgroups = {}
25
25
26 if src:
26 if src:
27 self._tcfg = src._tcfg.copy()
27 self._tcfg = src._tcfg.copy()
28 self._ucfg = src._ucfg.copy()
28 self._ucfg = src._ucfg.copy()
29 self._ocfg = src._ocfg.copy()
29 self._ocfg = src._ocfg.copy()
30 self._trustusers = src._trustusers.copy()
30 self._trustusers = src._trustusers.copy()
31 self._trustgroups = src._trustgroups.copy()
31 self._trustgroups = src._trustgroups.copy()
32 self.fixconfig()
32 self.fixconfig()
33 else:
33 else:
34 # we always trust global config files
34 # we always trust global config files
35 for f in util.rcpath():
35 for f in util.rcpath():
36 self.readconfig(f, trust=True)
36 self.readconfig(f, trust=True)
37
37
38 def copy(self):
38 def copy(self):
39 return self.__class__(self)
39 return self.__class__(self)
40
40
41 def _is_trusted(self, fp, f):
41 def _is_trusted(self, fp, f):
42 st = util.fstat(fp)
42 st = util.fstat(fp)
43 if util.isowner(fp, st):
43 if util.isowner(fp, st):
44 return True
44 return True
45
45
46 tusers, tgroups = self._trustusers, self._trustgroups
46 tusers, tgroups = self._trustusers, self._trustgroups
47 if '*' in tusers or '*' in tgroups:
47 if '*' in tusers or '*' in tgroups:
48 return True
48 return True
49
49
50 user = util.username(st.st_uid)
50 user = util.username(st.st_uid)
51 group = util.groupname(st.st_gid)
51 group = util.groupname(st.st_gid)
52 if user in tusers or group in tgroups or user == util.username():
52 if user in tusers or group in tgroups or user == util.username():
53 return True
53 return True
54
54
55 if self._reportuntrusted:
55 if self._reportuntrusted:
56 self.warn(_('Not trusting file %s from untrusted '
56 self.warn(_('Not trusting file %s from untrusted '
57 'user %s, group %s\n') % (f, user, group))
57 'user %s, group %s\n') % (f, user, group))
58 return False
58 return False
59
59
60 def readconfig(self, filename, root=None, trust=False,
60 def readconfig(self, filename, root=None, trust=False,
61 sections=None):
61 sections=None, remap=None):
62 try:
62 try:
63 fp = open(filename)
63 fp = open(filename)
64 except IOError:
64 except IOError:
65 if not sections: # ignore unless we were looking for something
65 if not sections: # ignore unless we were looking for something
66 return
66 return
67 raise
67 raise
68
68
69 cfg = config.config()
69 cfg = config.config()
70 trusted = sections or trust or self._is_trusted(fp, filename)
70 trusted = sections or trust or self._is_trusted(fp, filename)
71
71
72 try:
72 try:
73 cfg.read(filename, fp, sections=sections)
73 cfg.read(filename, fp, sections=sections, remap=remap)
74 except error.ConfigError, inst:
74 except error.ConfigError, inst:
75 if trusted:
75 if trusted:
76 raise
76 raise
77 self.warn(_("Ignored: %s\n") % str(inst))
77 self.warn(_("Ignored: %s\n") % str(inst))
78
78
79 if trusted:
79 if trusted:
80 self._tcfg.update(cfg)
80 self._tcfg.update(cfg)
81 self._tcfg.update(self._ocfg)
81 self._tcfg.update(self._ocfg)
82 self._ucfg.update(cfg)
82 self._ucfg.update(cfg)
83 self._ucfg.update(self._ocfg)
83 self._ucfg.update(self._ocfg)
84
84
85 if root is None:
85 if root is None:
86 root = os.path.expanduser('~')
86 root = os.path.expanduser('~')
87 self.fixconfig(root=root)
87 self.fixconfig(root=root)
88
88
89 def fixconfig(self, root=None):
89 def fixconfig(self, root=None):
90 # translate paths relative to root (or home) into absolute paths
90 # translate paths relative to root (or home) into absolute paths
91 root = root or os.getcwd()
91 root = root or os.getcwd()
92 for c in self._tcfg, self._ucfg, self._ocfg:
92 for c in self._tcfg, self._ucfg, self._ocfg:
93 for n, p in c.items('paths'):
93 for n, p in c.items('paths'):
94 if p and "://" not in p and not os.path.isabs(p):
94 if p and "://" not in p and not os.path.isabs(p):
95 c.set("paths", n, os.path.normpath(os.path.join(root, p)))
95 c.set("paths", n, os.path.normpath(os.path.join(root, p)))
96
96
97 # update ui options
97 # update ui options
98 self.debugflag = self.configbool('ui', 'debug')
98 self.debugflag = self.configbool('ui', 'debug')
99 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
99 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
100 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
100 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
101 if self.verbose and self.quiet:
101 if self.verbose and self.quiet:
102 self.quiet = self.verbose = False
102 self.quiet = self.verbose = False
103 self._reportuntrusted = self.configbool("ui", "report_untrusted", True)
103 self._reportuntrusted = self.configbool("ui", "report_untrusted", True)
104 self._traceback = self.configbool('ui', 'traceback', False)
104 self._traceback = self.configbool('ui', 'traceback', False)
105
105
106 # update trust information
106 # update trust information
107 for user in self.configlist('trusted', 'users'):
107 for user in self.configlist('trusted', 'users'):
108 self._trustusers[user] = 1
108 self._trustusers[user] = 1
109 for group in self.configlist('trusted', 'groups'):
109 for group in self.configlist('trusted', 'groups'):
110 self._trustgroups[group] = 1
110 self._trustgroups[group] = 1
111
111
112 def setconfig(self, section, name, value):
112 def setconfig(self, section, name, value):
113 for cfg in (self._ocfg, self._tcfg, self._ucfg):
113 for cfg in (self._ocfg, self._tcfg, self._ucfg):
114 cfg.set(section, name, value)
114 cfg.set(section, name, value)
115 self.fixconfig()
115 self.fixconfig()
116
116
117 def _data(self, untrusted):
117 def _data(self, untrusted):
118 return untrusted and self._ucfg or self._tcfg
118 return untrusted and self._ucfg or self._tcfg
119
119
120 def configsource(self, section, name, untrusted=False):
120 def configsource(self, section, name, untrusted=False):
121 return self._data(untrusted).source(section, name) or 'none'
121 return self._data(untrusted).source(section, name) or 'none'
122
122
123 def config(self, section, name, default=None, untrusted=False):
123 def config(self, section, name, default=None, untrusted=False):
124 value = self._data(untrusted).get(section, name, default)
124 value = self._data(untrusted).get(section, name, default)
125 if self.debugflag and not untrusted and self._reportuntrusted:
125 if self.debugflag and not untrusted and self._reportuntrusted:
126 uvalue = self._ucfg.get(section, name)
126 uvalue = self._ucfg.get(section, name)
127 if uvalue is not None and uvalue != value:
127 if uvalue is not None and uvalue != value:
128 self.debug(_("ignoring untrusted configuration option "
128 self.debug(_("ignoring untrusted configuration option "
129 "%s.%s = %s\n") % (section, name, uvalue))
129 "%s.%s = %s\n") % (section, name, uvalue))
130 return value
130 return value
131
131
132 def configbool(self, section, name, default=False, untrusted=False):
132 def configbool(self, section, name, default=False, untrusted=False):
133 v = self.config(section, name, None, untrusted)
133 v = self.config(section, name, None, untrusted)
134 if v == None:
134 if v == None:
135 return default
135 return default
136 if v.lower() not in _booleans:
136 if v.lower() not in _booleans:
137 raise error.ConfigError(_("%s.%s not a boolean ('%s')")
137 raise error.ConfigError(_("%s.%s not a boolean ('%s')")
138 % (section, name, v))
138 % (section, name, v))
139 return _booleans[v.lower()]
139 return _booleans[v.lower()]
140
140
141 def configlist(self, section, name, default=None, untrusted=False):
141 def configlist(self, section, name, default=None, untrusted=False):
142 """Return a list of comma/space separated strings"""
142 """Return a list of comma/space separated strings"""
143 result = self.config(section, name, untrusted=untrusted)
143 result = self.config(section, name, untrusted=untrusted)
144 if result is None:
144 if result is None:
145 result = default or []
145 result = default or []
146 if isinstance(result, basestring):
146 if isinstance(result, basestring):
147 result = result.replace(",", " ").split()
147 result = result.replace(",", " ").split()
148 return result
148 return result
149
149
150 def has_section(self, section, untrusted=False):
150 def has_section(self, section, untrusted=False):
151 '''tell whether section exists in config.'''
151 '''tell whether section exists in config.'''
152 return section in self._data(untrusted)
152 return section in self._data(untrusted)
153
153
154 def configitems(self, section, untrusted=False):
154 def configitems(self, section, untrusted=False):
155 items = self._data(untrusted).items(section)
155 items = self._data(untrusted).items(section)
156 if self.debugflag and not untrusted and self._reportuntrusted:
156 if self.debugflag and not untrusted and self._reportuntrusted:
157 for k, v in self._ucfg.items(section):
157 for k, v in self._ucfg.items(section):
158 if self._tcfg.get(section, k) != v:
158 if self._tcfg.get(section, k) != v:
159 self.debug(_("ignoring untrusted configuration option "
159 self.debug(_("ignoring untrusted configuration option "
160 "%s.%s = %s\n") % (section, k, v))
160 "%s.%s = %s\n") % (section, k, v))
161 return items
161 return items
162
162
163 def walkconfig(self, untrusted=False):
163 def walkconfig(self, untrusted=False):
164 cfg = self._data(untrusted)
164 cfg = self._data(untrusted)
165 for section in cfg.sections():
165 for section in cfg.sections():
166 for name, value in self.configitems(section, untrusted):
166 for name, value in self.configitems(section, untrusted):
167 yield section, name, str(value).replace('\n', '\\n')
167 yield section, name, str(value).replace('\n', '\\n')
168
168
169 def username(self):
169 def username(self):
170 """Return default username to be used in commits.
170 """Return default username to be used in commits.
171
171
172 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
172 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
173 and stop searching if one of these is set.
173 and stop searching if one of these is set.
174 If not found and ui.askusername is True, ask the user, else use
174 If not found and ui.askusername is True, ask the user, else use
175 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
175 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
176 """
176 """
177 user = os.environ.get("HGUSER")
177 user = os.environ.get("HGUSER")
178 if user is None:
178 if user is None:
179 user = self.config("ui", "username")
179 user = self.config("ui", "username")
180 if user is None:
180 if user is None:
181 user = os.environ.get("EMAIL")
181 user = os.environ.get("EMAIL")
182 if user is None and self.configbool("ui", "askusername"):
182 if user is None and self.configbool("ui", "askusername"):
183 user = self.prompt(_("enter a commit username:"), default=None)
183 user = self.prompt(_("enter a commit username:"), default=None)
184 if user is None:
184 if user is None:
185 try:
185 try:
186 user = '%s@%s' % (util.getuser(), socket.getfqdn())
186 user = '%s@%s' % (util.getuser(), socket.getfqdn())
187 self.warn(_("No username found, using '%s' instead\n") % user)
187 self.warn(_("No username found, using '%s' instead\n") % user)
188 except KeyError:
188 except KeyError:
189 pass
189 pass
190 if not user:
190 if not user:
191 raise util.Abort(_("Please specify a username."))
191 raise util.Abort(_("Please specify a username."))
192 if "\n" in user:
192 if "\n" in user:
193 raise util.Abort(_("username %s contains a newline\n") % repr(user))
193 raise util.Abort(_("username %s contains a newline\n") % repr(user))
194 return user
194 return user
195
195
196 def shortuser(self, user):
196 def shortuser(self, user):
197 """Return a short representation of a user name or email address."""
197 """Return a short representation of a user name or email address."""
198 if not self.verbose: user = util.shortuser(user)
198 if not self.verbose: user = util.shortuser(user)
199 return user
199 return user
200
200
201 def _path(self, loc):
201 def _path(self, loc):
202 p = self.config('paths', loc)
202 p = self.config('paths', loc)
203 if p and '%%' in p:
203 if p and '%%' in p:
204 ui.warn('(deprecated \'\%\%\' in path %s=%s from %s)\n' %
204 ui.warn('(deprecated \'\%\%\' in path %s=%s from %s)\n' %
205 (loc, p, self.configsource('paths', loc)))
205 (loc, p, self.configsource('paths', loc)))
206 p = p.replace('%%', '%')
206 p = p.replace('%%', '%')
207 return p
207 return p
208
208
209 def expandpath(self, loc, default=None):
209 def expandpath(self, loc, default=None):
210 """Return repository location relative to cwd or from [paths]"""
210 """Return repository location relative to cwd or from [paths]"""
211 if "://" in loc or os.path.isdir(os.path.join(loc, '.hg')):
211 if "://" in loc or os.path.isdir(os.path.join(loc, '.hg')):
212 return loc
212 return loc
213
213
214 path = self._path(loc)
214 path = self._path(loc)
215 if not path and default is not None:
215 if not path and default is not None:
216 path = self._path(default)
216 path = self._path(default)
217 return path or loc
217 return path or loc
218
218
219 def pushbuffer(self):
219 def pushbuffer(self):
220 self._buffers.append([])
220 self._buffers.append([])
221
221
222 def popbuffer(self):
222 def popbuffer(self):
223 return "".join(self._buffers.pop())
223 return "".join(self._buffers.pop())
224
224
225 def write(self, *args):
225 def write(self, *args):
226 if self._buffers:
226 if self._buffers:
227 self._buffers[-1].extend([str(a) for a in args])
227 self._buffers[-1].extend([str(a) for a in args])
228 else:
228 else:
229 for a in args:
229 for a in args:
230 sys.stdout.write(str(a))
230 sys.stdout.write(str(a))
231
231
232 def write_err(self, *args):
232 def write_err(self, *args):
233 try:
233 try:
234 if not sys.stdout.closed: sys.stdout.flush()
234 if not sys.stdout.closed: sys.stdout.flush()
235 for a in args:
235 for a in args:
236 sys.stderr.write(str(a))
236 sys.stderr.write(str(a))
237 # stderr may be buffered under win32 when redirected to files,
237 # stderr may be buffered under win32 when redirected to files,
238 # including stdout.
238 # including stdout.
239 if not sys.stderr.closed: sys.stderr.flush()
239 if not sys.stderr.closed: sys.stderr.flush()
240 except IOError, inst:
240 except IOError, inst:
241 if inst.errno != errno.EPIPE:
241 if inst.errno != errno.EPIPE:
242 raise
242 raise
243
243
244 def flush(self):
244 def flush(self):
245 try: sys.stdout.flush()
245 try: sys.stdout.flush()
246 except: pass
246 except: pass
247 try: sys.stderr.flush()
247 try: sys.stderr.flush()
248 except: pass
248 except: pass
249
249
250 def interactive(self):
250 def interactive(self):
251 return self.configbool("ui", "interactive") or sys.stdin.isatty()
251 return self.configbool("ui", "interactive") or sys.stdin.isatty()
252
252
253 def _readline(self, prompt=''):
253 def _readline(self, prompt=''):
254 if sys.stdin.isatty():
254 if sys.stdin.isatty():
255 try:
255 try:
256 # magically add command line editing support, where
256 # magically add command line editing support, where
257 # available
257 # available
258 import readline
258 import readline
259 # force demandimport to really load the module
259 # force demandimport to really load the module
260 readline.read_history_file
260 readline.read_history_file
261 # windows sometimes raises something other than ImportError
261 # windows sometimes raises something other than ImportError
262 except Exception:
262 except Exception:
263 pass
263 pass
264 line = raw_input(prompt)
264 line = raw_input(prompt)
265 # When stdin is in binary mode on Windows, it can cause
265 # When stdin is in binary mode on Windows, it can cause
266 # raw_input() to emit an extra trailing carriage return
266 # raw_input() to emit an extra trailing carriage return
267 if os.linesep == '\r\n' and line and line[-1] == '\r':
267 if os.linesep == '\r\n' and line and line[-1] == '\r':
268 line = line[:-1]
268 line = line[:-1]
269 return line
269 return line
270
270
271 def prompt(self, msg, choices=None, default="y"):
271 def prompt(self, msg, choices=None, default="y"):
272 """Prompt user with msg, read response, and ensure it matches
272 """Prompt user with msg, read response, and ensure it matches
273 one of the provided choices. choices is a sequence of acceptable
273 one of the provided choices. choices is a sequence of acceptable
274 responses with the format: ('&None', 'E&xec', 'Sym&link')
274 responses with the format: ('&None', 'E&xec', 'Sym&link')
275 No sequence implies no response checking. Responses are case
275 No sequence implies no response checking. Responses are case
276 insensitive. If ui is not interactive, the default is returned.
276 insensitive. If ui is not interactive, the default is returned.
277 """
277 """
278 if not self.interactive():
278 if not self.interactive():
279 self.note(msg, ' ', default, "\n")
279 self.note(msg, ' ', default, "\n")
280 return default
280 return default
281 while True:
281 while True:
282 try:
282 try:
283 r = self._readline(msg + ' ')
283 r = self._readline(msg + ' ')
284 if not r:
284 if not r:
285 return default
285 return default
286 if not choices:
286 if not choices:
287 return r
287 return r
288 resps = [s[s.index('&')+1].lower() for s in choices]
288 resps = [s[s.index('&')+1].lower() for s in choices]
289 if r.lower() in resps:
289 if r.lower() in resps:
290 return r.lower()
290 return r.lower()
291 else:
291 else:
292 self.write(_("unrecognized response\n"))
292 self.write(_("unrecognized response\n"))
293 except EOFError:
293 except EOFError:
294 raise util.Abort(_('response expected'))
294 raise util.Abort(_('response expected'))
295
295
296 def getpass(self, prompt=None, default=None):
296 def getpass(self, prompt=None, default=None):
297 if not self.interactive(): return default
297 if not self.interactive(): return default
298 try:
298 try:
299 return getpass.getpass(prompt or _('password: '))
299 return getpass.getpass(prompt or _('password: '))
300 except EOFError:
300 except EOFError:
301 raise util.Abort(_('response expected'))
301 raise util.Abort(_('response expected'))
302 def status(self, *msg):
302 def status(self, *msg):
303 if not self.quiet: self.write(*msg)
303 if not self.quiet: self.write(*msg)
304 def warn(self, *msg):
304 def warn(self, *msg):
305 self.write_err(*msg)
305 self.write_err(*msg)
306 def note(self, *msg):
306 def note(self, *msg):
307 if self.verbose: self.write(*msg)
307 if self.verbose: self.write(*msg)
308 def debug(self, *msg):
308 def debug(self, *msg):
309 if self.debugflag: self.write(*msg)
309 if self.debugflag: self.write(*msg)
310 def edit(self, text, user):
310 def edit(self, text, user):
311 (fd, name) = tempfile.mkstemp(prefix="hg-editor-", suffix=".txt",
311 (fd, name) = tempfile.mkstemp(prefix="hg-editor-", suffix=".txt",
312 text=True)
312 text=True)
313 try:
313 try:
314 f = os.fdopen(fd, "w")
314 f = os.fdopen(fd, "w")
315 f.write(text)
315 f.write(text)
316 f.close()
316 f.close()
317
317
318 editor = self.geteditor()
318 editor = self.geteditor()
319
319
320 util.system("%s \"%s\"" % (editor, name),
320 util.system("%s \"%s\"" % (editor, name),
321 environ={'HGUSER': user},
321 environ={'HGUSER': user},
322 onerr=util.Abort, errprefix=_("edit failed"))
322 onerr=util.Abort, errprefix=_("edit failed"))
323
323
324 f = open(name)
324 f = open(name)
325 t = f.read()
325 t = f.read()
326 f.close()
326 f.close()
327 t = re.sub("(?m)^HG:.*\n", "", t)
327 t = re.sub("(?m)^HG:.*\n", "", t)
328 finally:
328 finally:
329 os.unlink(name)
329 os.unlink(name)
330
330
331 return t
331 return t
332
332
333 def traceback(self):
333 def traceback(self):
334 '''print exception traceback if traceback printing enabled.
334 '''print exception traceback if traceback printing enabled.
335 only to call in exception handler. returns true if traceback
335 only to call in exception handler. returns true if traceback
336 printed.'''
336 printed.'''
337 if self._traceback:
337 if self._traceback:
338 traceback.print_exc()
338 traceback.print_exc()
339 return self._traceback
339 return self._traceback
340
340
341 def geteditor(self):
341 def geteditor(self):
342 '''return editor to use'''
342 '''return editor to use'''
343 return (os.environ.get("HGEDITOR") or
343 return (os.environ.get("HGEDITOR") or
344 self.config("ui", "editor") or
344 self.config("ui", "editor") or
345 os.environ.get("VISUAL") or
345 os.environ.get("VISUAL") or
346 os.environ.get("EDITOR", "vi"))
346 os.environ.get("EDITOR", "vi"))
General Comments 0
You need to be logged in to leave comments. Login now