##// END OF EJS Templates
config: also respect HGRCSKIPREPO in hgwebdir_mod...
marmoute -
r44729:bf23d6ee stable
parent child Browse files
Show More
@@ -1,579 +1,581
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 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9 from __future__ import absolute_import
9 from __future__ import absolute_import
10
10
11 import gc
11 import gc
12 import os
12 import os
13 import time
13 import time
14
14
15 from ..i18n import _
15 from ..i18n import _
16
16
17 from .common import (
17 from .common import (
18 ErrorResponse,
18 ErrorResponse,
19 HTTP_SERVER_ERROR,
19 HTTP_SERVER_ERROR,
20 cspvalues,
20 cspvalues,
21 get_contact,
21 get_contact,
22 get_mtime,
22 get_mtime,
23 ismember,
23 ismember,
24 paritygen,
24 paritygen,
25 staticfile,
25 staticfile,
26 statusmessage,
26 statusmessage,
27 )
27 )
28
28
29 from .. import (
29 from .. import (
30 configitems,
30 configitems,
31 encoding,
31 encoding,
32 error,
32 error,
33 extensions,
33 extensions,
34 hg,
34 hg,
35 pathutil,
35 pathutil,
36 profiling,
36 profiling,
37 pycompat,
37 pycompat,
38 rcutil,
38 registrar,
39 registrar,
39 scmutil,
40 scmutil,
40 templater,
41 templater,
41 templateutil,
42 templateutil,
42 ui as uimod,
43 ui as uimod,
43 util,
44 util,
44 )
45 )
45
46
46 from . import (
47 from . import (
47 hgweb_mod,
48 hgweb_mod,
48 request as requestmod,
49 request as requestmod,
49 webutil,
50 webutil,
50 wsgicgi,
51 wsgicgi,
51 )
52 )
52 from ..utils import dateutil
53 from ..utils import dateutil
53
54
54
55
55 def cleannames(items):
56 def cleannames(items):
56 return [(util.pconvert(name).strip(b'/'), path) for name, path in items]
57 return [(util.pconvert(name).strip(b'/'), path) for name, path in items]
57
58
58
59
59 def findrepos(paths):
60 def findrepos(paths):
60 repos = []
61 repos = []
61 for prefix, root in cleannames(paths):
62 for prefix, root in cleannames(paths):
62 roothead, roottail = os.path.split(root)
63 roothead, roottail = os.path.split(root)
63 # "foo = /bar/*" or "foo = /bar/**" lets every repo /bar/N in or below
64 # "foo = /bar/*" or "foo = /bar/**" lets every repo /bar/N in or below
64 # /bar/ be served as as foo/N .
65 # /bar/ be served as as foo/N .
65 # '*' will not search inside dirs with .hg (except .hg/patches),
66 # '*' will not search inside dirs with .hg (except .hg/patches),
66 # '**' will search inside dirs with .hg (and thus also find subrepos).
67 # '**' will search inside dirs with .hg (and thus also find subrepos).
67 try:
68 try:
68 recurse = {b'*': False, b'**': True}[roottail]
69 recurse = {b'*': False, b'**': True}[roottail]
69 except KeyError:
70 except KeyError:
70 repos.append((prefix, root))
71 repos.append((prefix, root))
71 continue
72 continue
72 roothead = os.path.normpath(os.path.abspath(roothead))
73 roothead = os.path.normpath(os.path.abspath(roothead))
73 paths = scmutil.walkrepos(roothead, followsym=True, recurse=recurse)
74 paths = scmutil.walkrepos(roothead, followsym=True, recurse=recurse)
74 repos.extend(urlrepos(prefix, roothead, paths))
75 repos.extend(urlrepos(prefix, roothead, paths))
75 return repos
76 return repos
76
77
77
78
78 def urlrepos(prefix, roothead, paths):
79 def urlrepos(prefix, roothead, paths):
79 """yield url paths and filesystem paths from a list of repo paths
80 """yield url paths and filesystem paths from a list of repo paths
80
81
81 >>> conv = lambda seq: [(v, util.pconvert(p)) for v,p in seq]
82 >>> conv = lambda seq: [(v, util.pconvert(p)) for v,p in seq]
82 >>> conv(urlrepos(b'hg', b'/opt', [b'/opt/r', b'/opt/r/r', b'/opt']))
83 >>> conv(urlrepos(b'hg', b'/opt', [b'/opt/r', b'/opt/r/r', b'/opt']))
83 [('hg/r', '/opt/r'), ('hg/r/r', '/opt/r/r'), ('hg', '/opt')]
84 [('hg/r', '/opt/r'), ('hg/r/r', '/opt/r/r'), ('hg', '/opt')]
84 >>> conv(urlrepos(b'', b'/opt', [b'/opt/r', b'/opt/r/r', b'/opt']))
85 >>> conv(urlrepos(b'', b'/opt', [b'/opt/r', b'/opt/r/r', b'/opt']))
85 [('r', '/opt/r'), ('r/r', '/opt/r/r'), ('', '/opt')]
86 [('r', '/opt/r'), ('r/r', '/opt/r/r'), ('', '/opt')]
86 """
87 """
87 for path in paths:
88 for path in paths:
88 path = os.path.normpath(path)
89 path = os.path.normpath(path)
89 yield (
90 yield (
90 prefix + b'/' + util.pconvert(path[len(roothead) :]).lstrip(b'/')
91 prefix + b'/' + util.pconvert(path[len(roothead) :]).lstrip(b'/')
91 ).strip(b'/'), path
92 ).strip(b'/'), path
92
93
93
94
94 def readallowed(ui, req):
95 def readallowed(ui, req):
95 """Check allow_read and deny_read config options of a repo's ui object
96 """Check allow_read and deny_read config options of a repo's ui object
96 to determine user permissions. By default, with neither option set (or
97 to determine user permissions. By default, with neither option set (or
97 both empty), allow all users to read the repo. There are two ways a
98 both empty), allow all users to read the repo. There are two ways a
98 user can be denied read access: (1) deny_read is not empty, and the
99 user can be denied read access: (1) deny_read is not empty, and the
99 user is unauthenticated or deny_read contains user (or *), and (2)
100 user is unauthenticated or deny_read contains user (or *), and (2)
100 allow_read is not empty and the user is not in allow_read. Return True
101 allow_read is not empty and the user is not in allow_read. Return True
101 if user is allowed to read the repo, else return False."""
102 if user is allowed to read the repo, else return False."""
102
103
103 user = req.remoteuser
104 user = req.remoteuser
104
105
105 deny_read = ui.configlist(b'web', b'deny_read', untrusted=True)
106 deny_read = ui.configlist(b'web', b'deny_read', untrusted=True)
106 if deny_read and (not user or ismember(ui, user, deny_read)):
107 if deny_read and (not user or ismember(ui, user, deny_read)):
107 return False
108 return False
108
109
109 allow_read = ui.configlist(b'web', b'allow_read', untrusted=True)
110 allow_read = ui.configlist(b'web', b'allow_read', untrusted=True)
110 # by default, allow reading if no allow_read option has been set
111 # by default, allow reading if no allow_read option has been set
111 if not allow_read or ismember(ui, user, allow_read):
112 if not allow_read or ismember(ui, user, allow_read):
112 return True
113 return True
113
114
114 return False
115 return False
115
116
116
117
117 def rawindexentries(ui, repos, req, subdir=b''):
118 def rawindexentries(ui, repos, req, subdir=b''):
118 descend = ui.configbool(b'web', b'descend')
119 descend = ui.configbool(b'web', b'descend')
119 collapse = ui.configbool(b'web', b'collapse')
120 collapse = ui.configbool(b'web', b'collapse')
120 seenrepos = set()
121 seenrepos = set()
121 seendirs = set()
122 seendirs = set()
122 for name, path in repos:
123 for name, path in repos:
123
124
124 if not name.startswith(subdir):
125 if not name.startswith(subdir):
125 continue
126 continue
126 name = name[len(subdir) :]
127 name = name[len(subdir) :]
127 directory = False
128 directory = False
128
129
129 if b'/' in name:
130 if b'/' in name:
130 if not descend:
131 if not descend:
131 continue
132 continue
132
133
133 nameparts = name.split(b'/')
134 nameparts = name.split(b'/')
134 rootname = nameparts[0]
135 rootname = nameparts[0]
135
136
136 if not collapse:
137 if not collapse:
137 pass
138 pass
138 elif rootname in seendirs:
139 elif rootname in seendirs:
139 continue
140 continue
140 elif rootname in seenrepos:
141 elif rootname in seenrepos:
141 pass
142 pass
142 else:
143 else:
143 directory = True
144 directory = True
144 name = rootname
145 name = rootname
145
146
146 # redefine the path to refer to the directory
147 # redefine the path to refer to the directory
147 discarded = b'/'.join(nameparts[1:])
148 discarded = b'/'.join(nameparts[1:])
148
149
149 # remove name parts plus accompanying slash
150 # remove name parts plus accompanying slash
150 path = path[: -len(discarded) - 1]
151 path = path[: -len(discarded) - 1]
151
152
152 try:
153 try:
153 hg.repository(ui, path)
154 hg.repository(ui, path)
154 directory = False
155 directory = False
155 except (IOError, error.RepoError):
156 except (IOError, error.RepoError):
156 pass
157 pass
157
158
158 parts = [
159 parts = [
159 req.apppath.strip(b'/'),
160 req.apppath.strip(b'/'),
160 subdir.strip(b'/'),
161 subdir.strip(b'/'),
161 name.strip(b'/'),
162 name.strip(b'/'),
162 ]
163 ]
163 url = b'/' + b'/'.join(p for p in parts if p) + b'/'
164 url = b'/' + b'/'.join(p for p in parts if p) + b'/'
164
165
165 # show either a directory entry or a repository
166 # show either a directory entry or a repository
166 if directory:
167 if directory:
167 # get the directory's time information
168 # get the directory's time information
168 try:
169 try:
169 d = (get_mtime(path), dateutil.makedate()[1])
170 d = (get_mtime(path), dateutil.makedate()[1])
170 except OSError:
171 except OSError:
171 continue
172 continue
172
173
173 # add '/' to the name to make it obvious that
174 # add '/' to the name to make it obvious that
174 # the entry is a directory, not a regular repository
175 # the entry is a directory, not a regular repository
175 row = {
176 row = {
176 b'contact': b"",
177 b'contact': b"",
177 b'contact_sort': b"",
178 b'contact_sort': b"",
178 b'name': name + b'/',
179 b'name': name + b'/',
179 b'name_sort': name,
180 b'name_sort': name,
180 b'url': url,
181 b'url': url,
181 b'description': b"",
182 b'description': b"",
182 b'description_sort': b"",
183 b'description_sort': b"",
183 b'lastchange': d,
184 b'lastchange': d,
184 b'lastchange_sort': d[1] - d[0],
185 b'lastchange_sort': d[1] - d[0],
185 b'archives': templateutil.mappinglist([]),
186 b'archives': templateutil.mappinglist([]),
186 b'isdirectory': True,
187 b'isdirectory': True,
187 b'labels': templateutil.hybridlist([], name=b'label'),
188 b'labels': templateutil.hybridlist([], name=b'label'),
188 }
189 }
189
190
190 seendirs.add(name)
191 seendirs.add(name)
191 yield row
192 yield row
192 continue
193 continue
193
194
194 u = ui.copy()
195 u = ui.copy()
195 try:
196 if rcutil.use_repo_hgrc():
196 u.readconfig(os.path.join(path, b'.hg', b'hgrc'))
197 try:
197 except Exception as e:
198 u.readconfig(os.path.join(path, b'.hg', b'hgrc'))
198 u.warn(_(b'error reading %s/.hg/hgrc: %s\n') % (path, e))
199 except Exception as e:
199 continue
200 u.warn(_(b'error reading %s/.hg/hgrc: %s\n') % (path, e))
201 continue
200
202
201 def get(section, name, default=uimod._unset):
203 def get(section, name, default=uimod._unset):
202 return u.config(section, name, default, untrusted=True)
204 return u.config(section, name, default, untrusted=True)
203
205
204 if u.configbool(b"web", b"hidden", untrusted=True):
206 if u.configbool(b"web", b"hidden", untrusted=True):
205 continue
207 continue
206
208
207 if not readallowed(u, req):
209 if not readallowed(u, req):
208 continue
210 continue
209
211
210 # update time with local timezone
212 # update time with local timezone
211 try:
213 try:
212 r = hg.repository(ui, path)
214 r = hg.repository(ui, path)
213 except IOError:
215 except IOError:
214 u.warn(_(b'error accessing repository at %s\n') % path)
216 u.warn(_(b'error accessing repository at %s\n') % path)
215 continue
217 continue
216 except error.RepoError:
218 except error.RepoError:
217 u.warn(_(b'error accessing repository at %s\n') % path)
219 u.warn(_(b'error accessing repository at %s\n') % path)
218 continue
220 continue
219 try:
221 try:
220 d = (get_mtime(r.spath), dateutil.makedate()[1])
222 d = (get_mtime(r.spath), dateutil.makedate()[1])
221 except OSError:
223 except OSError:
222 continue
224 continue
223
225
224 contact = get_contact(get)
226 contact = get_contact(get)
225 description = get(b"web", b"description")
227 description = get(b"web", b"description")
226 seenrepos.add(name)
228 seenrepos.add(name)
227 name = get(b"web", b"name", name)
229 name = get(b"web", b"name", name)
228 labels = u.configlist(b'web', b'labels', untrusted=True)
230 labels = u.configlist(b'web', b'labels', untrusted=True)
229 row = {
231 row = {
230 b'contact': contact or b"unknown",
232 b'contact': contact or b"unknown",
231 b'contact_sort': contact.upper() or b"unknown",
233 b'contact_sort': contact.upper() or b"unknown",
232 b'name': name,
234 b'name': name,
233 b'name_sort': name,
235 b'name_sort': name,
234 b'url': url,
236 b'url': url,
235 b'description': description or b"unknown",
237 b'description': description or b"unknown",
236 b'description_sort': description.upper() or b"unknown",
238 b'description_sort': description.upper() or b"unknown",
237 b'lastchange': d,
239 b'lastchange': d,
238 b'lastchange_sort': d[1] - d[0],
240 b'lastchange_sort': d[1] - d[0],
239 b'archives': webutil.archivelist(u, b"tip", url),
241 b'archives': webutil.archivelist(u, b"tip", url),
240 b'isdirectory': None,
242 b'isdirectory': None,
241 b'labels': templateutil.hybridlist(labels, name=b'label'),
243 b'labels': templateutil.hybridlist(labels, name=b'label'),
242 }
244 }
243
245
244 yield row
246 yield row
245
247
246
248
247 def _indexentriesgen(
249 def _indexentriesgen(
248 context, ui, repos, req, stripecount, sortcolumn, descending, subdir
250 context, ui, repos, req, stripecount, sortcolumn, descending, subdir
249 ):
251 ):
250 rows = rawindexentries(ui, repos, req, subdir=subdir)
252 rows = rawindexentries(ui, repos, req, subdir=subdir)
251
253
252 sortdefault = None, False
254 sortdefault = None, False
253
255
254 if sortcolumn and sortdefault != (sortcolumn, descending):
256 if sortcolumn and sortdefault != (sortcolumn, descending):
255 sortkey = b'%s_sort' % sortcolumn
257 sortkey = b'%s_sort' % sortcolumn
256 rows = sorted(rows, key=lambda x: x[sortkey], reverse=descending)
258 rows = sorted(rows, key=lambda x: x[sortkey], reverse=descending)
257
259
258 for row, parity in zip(rows, paritygen(stripecount)):
260 for row, parity in zip(rows, paritygen(stripecount)):
259 row[b'parity'] = parity
261 row[b'parity'] = parity
260 yield row
262 yield row
261
263
262
264
263 def indexentries(
265 def indexentries(
264 ui, repos, req, stripecount, sortcolumn=b'', descending=False, subdir=b''
266 ui, repos, req, stripecount, sortcolumn=b'', descending=False, subdir=b''
265 ):
267 ):
266 args = (ui, repos, req, stripecount, sortcolumn, descending, subdir)
268 args = (ui, repos, req, stripecount, sortcolumn, descending, subdir)
267 return templateutil.mappinggenerator(_indexentriesgen, args=args)
269 return templateutil.mappinggenerator(_indexentriesgen, args=args)
268
270
269
271
270 class hgwebdir(object):
272 class hgwebdir(object):
271 """HTTP server for multiple repositories.
273 """HTTP server for multiple repositories.
272
274
273 Given a configuration, different repositories will be served depending
275 Given a configuration, different repositories will be served depending
274 on the request path.
276 on the request path.
275
277
276 Instances are typically used as WSGI applications.
278 Instances are typically used as WSGI applications.
277 """
279 """
278
280
279 def __init__(self, conf, baseui=None):
281 def __init__(self, conf, baseui=None):
280 self.conf = conf
282 self.conf = conf
281 self.baseui = baseui
283 self.baseui = baseui
282 self.ui = None
284 self.ui = None
283 self.lastrefresh = 0
285 self.lastrefresh = 0
284 self.motd = None
286 self.motd = None
285 self.refresh()
287 self.refresh()
286 if not baseui:
288 if not baseui:
287 # set up environment for new ui
289 # set up environment for new ui
288 extensions.loadall(self.ui)
290 extensions.loadall(self.ui)
289 extensions.populateui(self.ui)
291 extensions.populateui(self.ui)
290
292
291 def refresh(self):
293 def refresh(self):
292 if self.ui:
294 if self.ui:
293 refreshinterval = self.ui.configint(b'web', b'refreshinterval')
295 refreshinterval = self.ui.configint(b'web', b'refreshinterval')
294 else:
296 else:
295 item = configitems.coreitems[b'web'][b'refreshinterval']
297 item = configitems.coreitems[b'web'][b'refreshinterval']
296 refreshinterval = item.default
298 refreshinterval = item.default
297
299
298 # refreshinterval <= 0 means to always refresh.
300 # refreshinterval <= 0 means to always refresh.
299 if (
301 if (
300 refreshinterval > 0
302 refreshinterval > 0
301 and self.lastrefresh + refreshinterval > time.time()
303 and self.lastrefresh + refreshinterval > time.time()
302 ):
304 ):
303 return
305 return
304
306
305 if self.baseui:
307 if self.baseui:
306 u = self.baseui.copy()
308 u = self.baseui.copy()
307 else:
309 else:
308 u = uimod.ui.load()
310 u = uimod.ui.load()
309 u.setconfig(b'ui', b'report_untrusted', b'off', b'hgwebdir')
311 u.setconfig(b'ui', b'report_untrusted', b'off', b'hgwebdir')
310 u.setconfig(b'ui', b'nontty', b'true', b'hgwebdir')
312 u.setconfig(b'ui', b'nontty', b'true', b'hgwebdir')
311 # displaying bundling progress bar while serving feels wrong and may
313 # displaying bundling progress bar while serving feels wrong and may
312 # break some wsgi implementations.
314 # break some wsgi implementations.
313 u.setconfig(b'progress', b'disable', b'true', b'hgweb')
315 u.setconfig(b'progress', b'disable', b'true', b'hgweb')
314
316
315 if not isinstance(self.conf, (dict, list, tuple)):
317 if not isinstance(self.conf, (dict, list, tuple)):
316 map = {b'paths': b'hgweb-paths'}
318 map = {b'paths': b'hgweb-paths'}
317 if not os.path.exists(self.conf):
319 if not os.path.exists(self.conf):
318 raise error.Abort(_(b'config file %s not found!') % self.conf)
320 raise error.Abort(_(b'config file %s not found!') % self.conf)
319 u.readconfig(self.conf, remap=map, trust=True)
321 u.readconfig(self.conf, remap=map, trust=True)
320 paths = []
322 paths = []
321 for name, ignored in u.configitems(b'hgweb-paths'):
323 for name, ignored in u.configitems(b'hgweb-paths'):
322 for path in u.configlist(b'hgweb-paths', name):
324 for path in u.configlist(b'hgweb-paths', name):
323 paths.append((name, path))
325 paths.append((name, path))
324 elif isinstance(self.conf, (list, tuple)):
326 elif isinstance(self.conf, (list, tuple)):
325 paths = self.conf
327 paths = self.conf
326 elif isinstance(self.conf, dict):
328 elif isinstance(self.conf, dict):
327 paths = self.conf.items()
329 paths = self.conf.items()
328 extensions.populateui(u)
330 extensions.populateui(u)
329
331
330 repos = findrepos(paths)
332 repos = findrepos(paths)
331 for prefix, root in u.configitems(b'collections'):
333 for prefix, root in u.configitems(b'collections'):
332 prefix = util.pconvert(prefix)
334 prefix = util.pconvert(prefix)
333 for path in scmutil.walkrepos(root, followsym=True):
335 for path in scmutil.walkrepos(root, followsym=True):
334 repo = os.path.normpath(path)
336 repo = os.path.normpath(path)
335 name = util.pconvert(repo)
337 name = util.pconvert(repo)
336 if name.startswith(prefix):
338 if name.startswith(prefix):
337 name = name[len(prefix) :]
339 name = name[len(prefix) :]
338 repos.append((name.lstrip(b'/'), repo))
340 repos.append((name.lstrip(b'/'), repo))
339
341
340 self.repos = repos
342 self.repos = repos
341 self.ui = u
343 self.ui = u
342 encoding.encoding = self.ui.config(b'web', b'encoding')
344 encoding.encoding = self.ui.config(b'web', b'encoding')
343 self.style = self.ui.config(b'web', b'style')
345 self.style = self.ui.config(b'web', b'style')
344 self.templatepath = self.ui.config(
346 self.templatepath = self.ui.config(
345 b'web', b'templates', untrusted=False
347 b'web', b'templates', untrusted=False
346 )
348 )
347 self.stripecount = self.ui.config(b'web', b'stripes')
349 self.stripecount = self.ui.config(b'web', b'stripes')
348 if self.stripecount:
350 if self.stripecount:
349 self.stripecount = int(self.stripecount)
351 self.stripecount = int(self.stripecount)
350 prefix = self.ui.config(b'web', b'prefix')
352 prefix = self.ui.config(b'web', b'prefix')
351 if prefix.startswith(b'/'):
353 if prefix.startswith(b'/'):
352 prefix = prefix[1:]
354 prefix = prefix[1:]
353 if prefix.endswith(b'/'):
355 if prefix.endswith(b'/'):
354 prefix = prefix[:-1]
356 prefix = prefix[:-1]
355 self.prefix = prefix
357 self.prefix = prefix
356 self.lastrefresh = time.time()
358 self.lastrefresh = time.time()
357
359
358 def run(self):
360 def run(self):
359 if not encoding.environ.get(b'GATEWAY_INTERFACE', b'').startswith(
361 if not encoding.environ.get(b'GATEWAY_INTERFACE', b'').startswith(
360 b"CGI/1."
362 b"CGI/1."
361 ):
363 ):
362 raise RuntimeError(
364 raise RuntimeError(
363 b"This function is only intended to be "
365 b"This function is only intended to be "
364 b"called while running as a CGI script."
366 b"called while running as a CGI script."
365 )
367 )
366 wsgicgi.launch(self)
368 wsgicgi.launch(self)
367
369
368 def __call__(self, env, respond):
370 def __call__(self, env, respond):
369 baseurl = self.ui.config(b'web', b'baseurl')
371 baseurl = self.ui.config(b'web', b'baseurl')
370 req = requestmod.parserequestfromenv(env, altbaseurl=baseurl)
372 req = requestmod.parserequestfromenv(env, altbaseurl=baseurl)
371 res = requestmod.wsgiresponse(req, respond)
373 res = requestmod.wsgiresponse(req, respond)
372
374
373 return self.run_wsgi(req, res)
375 return self.run_wsgi(req, res)
374
376
375 def run_wsgi(self, req, res):
377 def run_wsgi(self, req, res):
376 profile = self.ui.configbool(b'profiling', b'enabled')
378 profile = self.ui.configbool(b'profiling', b'enabled')
377 with profiling.profile(self.ui, enabled=profile):
379 with profiling.profile(self.ui, enabled=profile):
378 try:
380 try:
379 for r in self._runwsgi(req, res):
381 for r in self._runwsgi(req, res):
380 yield r
382 yield r
381 finally:
383 finally:
382 # There are known cycles in localrepository that prevent
384 # There are known cycles in localrepository that prevent
383 # those objects (and tons of held references) from being
385 # those objects (and tons of held references) from being
384 # collected through normal refcounting. We mitigate those
386 # collected through normal refcounting. We mitigate those
385 # leaks by performing an explicit GC on every request.
387 # leaks by performing an explicit GC on every request.
386 # TODO remove this once leaks are fixed.
388 # TODO remove this once leaks are fixed.
387 # TODO only run this on requests that create localrepository
389 # TODO only run this on requests that create localrepository
388 # instances instead of every request.
390 # instances instead of every request.
389 gc.collect()
391 gc.collect()
390
392
391 def _runwsgi(self, req, res):
393 def _runwsgi(self, req, res):
392 try:
394 try:
393 self.refresh()
395 self.refresh()
394
396
395 csp, nonce = cspvalues(self.ui)
397 csp, nonce = cspvalues(self.ui)
396 if csp:
398 if csp:
397 res.headers[b'Content-Security-Policy'] = csp
399 res.headers[b'Content-Security-Policy'] = csp
398
400
399 virtual = req.dispatchpath.strip(b'/')
401 virtual = req.dispatchpath.strip(b'/')
400 tmpl = self.templater(req, nonce)
402 tmpl = self.templater(req, nonce)
401 ctype = tmpl.render(b'mimetype', {b'encoding': encoding.encoding})
403 ctype = tmpl.render(b'mimetype', {b'encoding': encoding.encoding})
402
404
403 # Global defaults. These can be overridden by any handler.
405 # Global defaults. These can be overridden by any handler.
404 res.status = b'200 Script output follows'
406 res.status = b'200 Script output follows'
405 res.headers[b'Content-Type'] = ctype
407 res.headers[b'Content-Type'] = ctype
406
408
407 # a static file
409 # a static file
408 if virtual.startswith(b'static/') or b'static' in req.qsparams:
410 if virtual.startswith(b'static/') or b'static' in req.qsparams:
409 if virtual.startswith(b'static/'):
411 if virtual.startswith(b'static/'):
410 fname = virtual[7:]
412 fname = virtual[7:]
411 else:
413 else:
412 fname = req.qsparams[b'static']
414 fname = req.qsparams[b'static']
413 static = self.ui.config(b"web", b"static", untrusted=False)
415 static = self.ui.config(b"web", b"static", untrusted=False)
414 if not static:
416 if not static:
415 tp = self.templatepath or templater.templatepaths()
417 tp = self.templatepath or templater.templatepaths()
416 if isinstance(tp, bytes):
418 if isinstance(tp, bytes):
417 tp = [tp]
419 tp = [tp]
418 static = [os.path.join(p, b'static') for p in tp]
420 static = [os.path.join(p, b'static') for p in tp]
419
421
420 staticfile(static, fname, res)
422 staticfile(static, fname, res)
421 return res.sendresponse()
423 return res.sendresponse()
422
424
423 # top-level index
425 # top-level index
424
426
425 repos = dict(self.repos)
427 repos = dict(self.repos)
426
428
427 if (not virtual or virtual == b'index') and virtual not in repos:
429 if (not virtual or virtual == b'index') and virtual not in repos:
428 return self.makeindex(req, res, tmpl)
430 return self.makeindex(req, res, tmpl)
429
431
430 # nested indexes and hgwebs
432 # nested indexes and hgwebs
431
433
432 if virtual.endswith(b'/index') and virtual not in repos:
434 if virtual.endswith(b'/index') and virtual not in repos:
433 subdir = virtual[: -len(b'index')]
435 subdir = virtual[: -len(b'index')]
434 if any(r.startswith(subdir) for r in repos):
436 if any(r.startswith(subdir) for r in repos):
435 return self.makeindex(req, res, tmpl, subdir)
437 return self.makeindex(req, res, tmpl, subdir)
436
438
437 def _virtualdirs():
439 def _virtualdirs():
438 # Check the full virtual path, and each parent
440 # Check the full virtual path, and each parent
439 yield virtual
441 yield virtual
440 for p in pathutil.finddirs(virtual):
442 for p in pathutil.finddirs(virtual):
441 yield p
443 yield p
442
444
443 for virtualrepo in _virtualdirs():
445 for virtualrepo in _virtualdirs():
444 real = repos.get(virtualrepo)
446 real = repos.get(virtualrepo)
445 if real:
447 if real:
446 # Re-parse the WSGI environment to take into account our
448 # Re-parse the WSGI environment to take into account our
447 # repository path component.
449 # repository path component.
448 uenv = req.rawenv
450 uenv = req.rawenv
449 if pycompat.ispy3:
451 if pycompat.ispy3:
450 uenv = {
452 uenv = {
451 k.decode('latin1'): v
453 k.decode('latin1'): v
452 for k, v in pycompat.iteritems(uenv)
454 for k, v in pycompat.iteritems(uenv)
453 }
455 }
454 req = requestmod.parserequestfromenv(
456 req = requestmod.parserequestfromenv(
455 uenv,
457 uenv,
456 reponame=virtualrepo,
458 reponame=virtualrepo,
457 altbaseurl=self.ui.config(b'web', b'baseurl'),
459 altbaseurl=self.ui.config(b'web', b'baseurl'),
458 # Reuse wrapped body file object otherwise state
460 # Reuse wrapped body file object otherwise state
459 # tracking can get confused.
461 # tracking can get confused.
460 bodyfh=req.bodyfh,
462 bodyfh=req.bodyfh,
461 )
463 )
462 try:
464 try:
463 # ensure caller gets private copy of ui
465 # ensure caller gets private copy of ui
464 repo = hg.repository(self.ui.copy(), real)
466 repo = hg.repository(self.ui.copy(), real)
465 return hgweb_mod.hgweb(repo).run_wsgi(req, res)
467 return hgweb_mod.hgweb(repo).run_wsgi(req, res)
466 except IOError as inst:
468 except IOError as inst:
467 msg = encoding.strtolocal(inst.strerror)
469 msg = encoding.strtolocal(inst.strerror)
468 raise ErrorResponse(HTTP_SERVER_ERROR, msg)
470 raise ErrorResponse(HTTP_SERVER_ERROR, msg)
469 except error.RepoError as inst:
471 except error.RepoError as inst:
470 raise ErrorResponse(HTTP_SERVER_ERROR, bytes(inst))
472 raise ErrorResponse(HTTP_SERVER_ERROR, bytes(inst))
471
473
472 # browse subdirectories
474 # browse subdirectories
473 subdir = virtual + b'/'
475 subdir = virtual + b'/'
474 if [r for r in repos if r.startswith(subdir)]:
476 if [r for r in repos if r.startswith(subdir)]:
475 return self.makeindex(req, res, tmpl, subdir)
477 return self.makeindex(req, res, tmpl, subdir)
476
478
477 # prefixes not found
479 # prefixes not found
478 res.status = b'404 Not Found'
480 res.status = b'404 Not Found'
479 res.setbodygen(tmpl.generate(b'notfound', {b'repo': virtual}))
481 res.setbodygen(tmpl.generate(b'notfound', {b'repo': virtual}))
480 return res.sendresponse()
482 return res.sendresponse()
481
483
482 except ErrorResponse as e:
484 except ErrorResponse as e:
483 res.status = statusmessage(e.code, pycompat.bytestr(e))
485 res.status = statusmessage(e.code, pycompat.bytestr(e))
484 res.setbodygen(
486 res.setbodygen(
485 tmpl.generate(b'error', {b'error': e.message or b''})
487 tmpl.generate(b'error', {b'error': e.message or b''})
486 )
488 )
487 return res.sendresponse()
489 return res.sendresponse()
488 finally:
490 finally:
489 del tmpl
491 del tmpl
490
492
491 def makeindex(self, req, res, tmpl, subdir=b""):
493 def makeindex(self, req, res, tmpl, subdir=b""):
492 self.refresh()
494 self.refresh()
493 sortable = [b"name", b"description", b"contact", b"lastchange"]
495 sortable = [b"name", b"description", b"contact", b"lastchange"]
494 sortcolumn, descending = None, False
496 sortcolumn, descending = None, False
495 if b'sort' in req.qsparams:
497 if b'sort' in req.qsparams:
496 sortcolumn = req.qsparams[b'sort']
498 sortcolumn = req.qsparams[b'sort']
497 descending = sortcolumn.startswith(b'-')
499 descending = sortcolumn.startswith(b'-')
498 if descending:
500 if descending:
499 sortcolumn = sortcolumn[1:]
501 sortcolumn = sortcolumn[1:]
500 if sortcolumn not in sortable:
502 if sortcolumn not in sortable:
501 sortcolumn = b""
503 sortcolumn = b""
502
504
503 sort = [
505 sort = [
504 (
506 (
505 b"sort_%s" % column,
507 b"sort_%s" % column,
506 b"%s%s"
508 b"%s%s"
507 % (
509 % (
508 (not descending and column == sortcolumn) and b"-" or b"",
510 (not descending and column == sortcolumn) and b"-" or b"",
509 column,
511 column,
510 ),
512 ),
511 )
513 )
512 for column in sortable
514 for column in sortable
513 ]
515 ]
514
516
515 self.refresh()
517 self.refresh()
516
518
517 entries = indexentries(
519 entries = indexentries(
518 self.ui,
520 self.ui,
519 self.repos,
521 self.repos,
520 req,
522 req,
521 self.stripecount,
523 self.stripecount,
522 sortcolumn=sortcolumn,
524 sortcolumn=sortcolumn,
523 descending=descending,
525 descending=descending,
524 subdir=subdir,
526 subdir=subdir,
525 )
527 )
526
528
527 mapping = {
529 mapping = {
528 b'entries': entries,
530 b'entries': entries,
529 b'subdir': subdir,
531 b'subdir': subdir,
530 b'pathdef': hgweb_mod.makebreadcrumb(b'/' + subdir, self.prefix),
532 b'pathdef': hgweb_mod.makebreadcrumb(b'/' + subdir, self.prefix),
531 b'sortcolumn': sortcolumn,
533 b'sortcolumn': sortcolumn,
532 b'descending': descending,
534 b'descending': descending,
533 }
535 }
534 mapping.update(sort)
536 mapping.update(sort)
535 res.setbodygen(tmpl.generate(b'index', mapping))
537 res.setbodygen(tmpl.generate(b'index', mapping))
536 return res.sendresponse()
538 return res.sendresponse()
537
539
538 def templater(self, req, nonce):
540 def templater(self, req, nonce):
539 def config(section, name, default=uimod._unset, untrusted=True):
541 def config(section, name, default=uimod._unset, untrusted=True):
540 return self.ui.config(section, name, default, untrusted)
542 return self.ui.config(section, name, default, untrusted)
541
543
542 vars = {}
544 vars = {}
543 styles, (style, mapfile) = hgweb_mod.getstyle(
545 styles, (style, mapfile) = hgweb_mod.getstyle(
544 req, config, self.templatepath
546 req, config, self.templatepath
545 )
547 )
546 if style == styles[0]:
548 if style == styles[0]:
547 vars[b'style'] = style
549 vars[b'style'] = style
548
550
549 sessionvars = webutil.sessionvars(vars, b'?')
551 sessionvars = webutil.sessionvars(vars, b'?')
550 logourl = config(b'web', b'logourl')
552 logourl = config(b'web', b'logourl')
551 logoimg = config(b'web', b'logoimg')
553 logoimg = config(b'web', b'logoimg')
552 staticurl = (
554 staticurl = (
553 config(b'web', b'staticurl')
555 config(b'web', b'staticurl')
554 or req.apppath.rstrip(b'/') + b'/static/'
556 or req.apppath.rstrip(b'/') + b'/static/'
555 )
557 )
556 if not staticurl.endswith(b'/'):
558 if not staticurl.endswith(b'/'):
557 staticurl += b'/'
559 staticurl += b'/'
558
560
559 defaults = {
561 defaults = {
560 b"encoding": encoding.encoding,
562 b"encoding": encoding.encoding,
561 b"url": req.apppath + b'/',
563 b"url": req.apppath + b'/',
562 b"logourl": logourl,
564 b"logourl": logourl,
563 b"logoimg": logoimg,
565 b"logoimg": logoimg,
564 b"staticurl": staticurl,
566 b"staticurl": staticurl,
565 b"sessionvars": sessionvars,
567 b"sessionvars": sessionvars,
566 b"style": style,
568 b"style": style,
567 b"nonce": nonce,
569 b"nonce": nonce,
568 }
570 }
569 templatekeyword = registrar.templatekeyword(defaults)
571 templatekeyword = registrar.templatekeyword(defaults)
570
572
571 @templatekeyword(b'motd', requires=())
573 @templatekeyword(b'motd', requires=())
572 def motd(context, mapping):
574 def motd(context, mapping):
573 if self.motd is not None:
575 if self.motd is not None:
574 yield self.motd
576 yield self.motd
575 else:
577 else:
576 yield config(b'web', b'motd')
578 yield config(b'web', b'motd')
577
579
578 tmpl = templater.templater.frommapfile(mapfile, defaults=defaults)
580 tmpl = templater.templater.frommapfile(mapfile, defaults=defaults)
579 return tmpl
581 return tmpl
@@ -1,283 +1,301
1 Use hgrc within $TESTTMP
1 Use hgrc within $TESTTMP
2
2
3 $ HGRCPATH=`pwd`/hgrc
3 $ HGRCPATH=`pwd`/hgrc
4 $ export HGRCPATH
4 $ export HGRCPATH
5
5
6 hide outer repo
6 hide outer repo
7 $ hg init
7 $ hg init
8
8
9 Use an alternate var for scribbling on hgrc to keep check-code from
9 Use an alternate var for scribbling on hgrc to keep check-code from
10 complaining about the important settings we may be overwriting:
10 complaining about the important settings we may be overwriting:
11
11
12 $ HGRC=`pwd`/hgrc
12 $ HGRC=`pwd`/hgrc
13 $ export HGRC
13 $ export HGRC
14
14
15 Basic syntax error
15 Basic syntax error
16
16
17 $ echo "invalid" > $HGRC
17 $ echo "invalid" > $HGRC
18 $ hg version
18 $ hg version
19 hg: parse error at $TESTTMP/hgrc:1: invalid
19 hg: parse error at $TESTTMP/hgrc:1: invalid
20 [255]
20 [255]
21 $ echo "" > $HGRC
21 $ echo "" > $HGRC
22
22
23 Issue1199: Can't use '%' in hgrc (eg url encoded username)
23 Issue1199: Can't use '%' in hgrc (eg url encoded username)
24
24
25 $ hg init "foo%bar"
25 $ hg init "foo%bar"
26 $ hg clone "foo%bar" foobar
26 $ hg clone "foo%bar" foobar
27 updating to branch default
27 updating to branch default
28 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
28 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
29 $ cd foobar
29 $ cd foobar
30 $ cat .hg/hgrc
30 $ cat .hg/hgrc
31 # example repository config (see 'hg help config' for more info)
31 # example repository config (see 'hg help config' for more info)
32 [paths]
32 [paths]
33 default = $TESTTMP/foo%bar
33 default = $TESTTMP/foo%bar
34
34
35 # path aliases to other clones of this repo in URLs or filesystem paths
35 # path aliases to other clones of this repo in URLs or filesystem paths
36 # (see 'hg help config.paths' for more info)
36 # (see 'hg help config.paths' for more info)
37 #
37 #
38 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
38 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
39 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
39 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
40 # my-clone = /home/jdoe/jdoes-clone
40 # my-clone = /home/jdoe/jdoes-clone
41
41
42 [ui]
42 [ui]
43 # name and email (local to this repository, optional), e.g.
43 # name and email (local to this repository, optional), e.g.
44 # username = Jane Doe <jdoe@example.com>
44 # username = Jane Doe <jdoe@example.com>
45 $ hg paths
45 $ hg paths
46 default = $TESTTMP/foo%bar
46 default = $TESTTMP/foo%bar
47 $ hg showconfig
47 $ hg showconfig
48 bundle.mainreporoot=$TESTTMP/foobar
48 bundle.mainreporoot=$TESTTMP/foobar
49 paths.default=$TESTTMP/foo%bar
49 paths.default=$TESTTMP/foo%bar
50 $ cd ..
50 $ cd ..
51
51
52 Check %include
52 Check %include
53
53
54 $ echo '[section]' > $TESTTMP/included
54 $ echo '[section]' > $TESTTMP/included
55 $ echo 'option = value' >> $TESTTMP/included
55 $ echo 'option = value' >> $TESTTMP/included
56 $ echo '%include $TESTTMP/included' >> $HGRC
56 $ echo '%include $TESTTMP/included' >> $HGRC
57 $ hg showconfig section
57 $ hg showconfig section
58 section.option=value
58 section.option=value
59 #if no-windows
59 #if no-windows
60 $ chmod u-r $TESTTMP/included
60 $ chmod u-r $TESTTMP/included
61 $ hg showconfig section
61 $ hg showconfig section
62 hg: parse error at $TESTTMP/hgrc:2: cannot include $TESTTMP/included (Permission denied)
62 hg: parse error at $TESTTMP/hgrc:2: cannot include $TESTTMP/included (Permission denied)
63 [255]
63 [255]
64 #endif
64 #endif
65
65
66 issue1829: wrong indentation
66 issue1829: wrong indentation
67
67
68 $ echo '[foo]' > $HGRC
68 $ echo '[foo]' > $HGRC
69 $ echo ' x = y' >> $HGRC
69 $ echo ' x = y' >> $HGRC
70 $ hg version
70 $ hg version
71 hg: parse error at $TESTTMP/hgrc:2: x = y
71 hg: parse error at $TESTTMP/hgrc:2: x = y
72 unexpected leading whitespace
72 unexpected leading whitespace
73 [255]
73 [255]
74
74
75 $ "$PYTHON" -c "from __future__ import print_function; print('[foo]\nbar = a\n b\n c \n de\n fg \nbaz = bif cb \n')" \
75 $ "$PYTHON" -c "from __future__ import print_function; print('[foo]\nbar = a\n b\n c \n de\n fg \nbaz = bif cb \n')" \
76 > > $HGRC
76 > > $HGRC
77 $ hg showconfig foo
77 $ hg showconfig foo
78 foo.bar=a\nb\nc\nde\nfg
78 foo.bar=a\nb\nc\nde\nfg
79 foo.baz=bif cb
79 foo.baz=bif cb
80
80
81 $ FAKEPATH=/path/to/nowhere
81 $ FAKEPATH=/path/to/nowhere
82 $ export FAKEPATH
82 $ export FAKEPATH
83 $ echo '%include $FAKEPATH/no-such-file' > $HGRC
83 $ echo '%include $FAKEPATH/no-such-file' > $HGRC
84 $ hg version
84 $ hg version
85 Mercurial Distributed SCM (version *) (glob)
85 Mercurial Distributed SCM (version *) (glob)
86 (see https://mercurial-scm.org for more information)
86 (see https://mercurial-scm.org for more information)
87
87
88 Copyright (C) 2005-* Matt Mackall and others (glob)
88 Copyright (C) 2005-* Matt Mackall and others (glob)
89 This is free software; see the source for copying conditions. There is NO
89 This is free software; see the source for copying conditions. There is NO
90 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
90 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
91 $ unset FAKEPATH
91 $ unset FAKEPATH
92
92
93 make sure global options given on the cmdline take precedence
93 make sure global options given on the cmdline take precedence
94
94
95 $ hg showconfig --config ui.verbose=True --quiet
95 $ hg showconfig --config ui.verbose=True --quiet
96 bundle.mainreporoot=$TESTTMP
96 bundle.mainreporoot=$TESTTMP
97 ui.verbose=False
97 ui.verbose=False
98 ui.debug=False
98 ui.debug=False
99 ui.quiet=True
99 ui.quiet=True
100
100
101 $ touch foobar/untracked
101 $ touch foobar/untracked
102 $ cat >> foobar/.hg/hgrc <<EOF
102 $ cat >> foobar/.hg/hgrc <<EOF
103 > [ui]
103 > [ui]
104 > verbose=True
104 > verbose=True
105 > EOF
105 > EOF
106 $ hg -R foobar st -q
106 $ hg -R foobar st -q
107
107
108 username expansion
108 username expansion
109
109
110 $ olduser=$HGUSER
110 $ olduser=$HGUSER
111 $ unset HGUSER
111 $ unset HGUSER
112
112
113 $ FAKEUSER='John Doe'
113 $ FAKEUSER='John Doe'
114 $ export FAKEUSER
114 $ export FAKEUSER
115 $ echo '[ui]' > $HGRC
115 $ echo '[ui]' > $HGRC
116 $ echo 'username = $FAKEUSER' >> $HGRC
116 $ echo 'username = $FAKEUSER' >> $HGRC
117
117
118 $ hg init usertest
118 $ hg init usertest
119 $ cd usertest
119 $ cd usertest
120 $ touch bar
120 $ touch bar
121 $ hg commit --addremove --quiet -m "added bar"
121 $ hg commit --addremove --quiet -m "added bar"
122 $ hg log --template "{author}\n"
122 $ hg log --template "{author}\n"
123 John Doe
123 John Doe
124 $ cd ..
124 $ cd ..
125
125
126 $ hg showconfig
126 $ hg showconfig
127 bundle.mainreporoot=$TESTTMP
127 bundle.mainreporoot=$TESTTMP
128 ui.username=$FAKEUSER
128 ui.username=$FAKEUSER
129
129
130 $ unset FAKEUSER
130 $ unset FAKEUSER
131 $ HGUSER=$olduser
131 $ HGUSER=$olduser
132 $ export HGUSER
132 $ export HGUSER
133
133
134 showconfig with multiple arguments
134 showconfig with multiple arguments
135
135
136 $ echo "[alias]" > $HGRC
136 $ echo "[alias]" > $HGRC
137 $ echo "log = log -g" >> $HGRC
137 $ echo "log = log -g" >> $HGRC
138 $ echo "[defaults]" >> $HGRC
138 $ echo "[defaults]" >> $HGRC
139 $ echo "identify = -n" >> $HGRC
139 $ echo "identify = -n" >> $HGRC
140 $ hg showconfig alias defaults
140 $ hg showconfig alias defaults
141 alias.log=log -g
141 alias.log=log -g
142 defaults.identify=-n
142 defaults.identify=-n
143 $ hg showconfig alias alias
143 $ hg showconfig alias alias
144 alias.log=log -g
144 alias.log=log -g
145 $ hg showconfig alias.log alias.log
145 $ hg showconfig alias.log alias.log
146 alias.log=log -g
146 alias.log=log -g
147 $ hg showconfig alias defaults.identify
147 $ hg showconfig alias defaults.identify
148 alias.log=log -g
148 alias.log=log -g
149 defaults.identify=-n
149 defaults.identify=-n
150 $ hg showconfig alias.log defaults.identify
150 $ hg showconfig alias.log defaults.identify
151 alias.log=log -g
151 alias.log=log -g
152 defaults.identify=-n
152 defaults.identify=-n
153
153
154 HGPLAIN
154 HGPLAIN
155
155
156 $ echo "[ui]" > $HGRC
156 $ echo "[ui]" > $HGRC
157 $ echo "debug=true" >> $HGRC
157 $ echo "debug=true" >> $HGRC
158 $ echo "fallbackencoding=ASCII" >> $HGRC
158 $ echo "fallbackencoding=ASCII" >> $HGRC
159 $ echo "quiet=true" >> $HGRC
159 $ echo "quiet=true" >> $HGRC
160 $ echo "slash=true" >> $HGRC
160 $ echo "slash=true" >> $HGRC
161 $ echo "traceback=true" >> $HGRC
161 $ echo "traceback=true" >> $HGRC
162 $ echo "verbose=true" >> $HGRC
162 $ echo "verbose=true" >> $HGRC
163 $ echo "style=~/.hgstyle" >> $HGRC
163 $ echo "style=~/.hgstyle" >> $HGRC
164 $ echo "logtemplate={node}" >> $HGRC
164 $ echo "logtemplate={node}" >> $HGRC
165 $ echo "[defaults]" >> $HGRC
165 $ echo "[defaults]" >> $HGRC
166 $ echo "identify=-n" >> $HGRC
166 $ echo "identify=-n" >> $HGRC
167 $ echo "[alias]" >> $HGRC
167 $ echo "[alias]" >> $HGRC
168 $ echo "log=log -g" >> $HGRC
168 $ echo "log=log -g" >> $HGRC
169
169
170 customized hgrc
170 customized hgrc
171
171
172 $ hg showconfig
172 $ hg showconfig
173 read config from: $TESTTMP/hgrc
173 read config from: $TESTTMP/hgrc
174 $TESTTMP/hgrc:13: alias.log=log -g
174 $TESTTMP/hgrc:13: alias.log=log -g
175 repo: bundle.mainreporoot=$TESTTMP
175 repo: bundle.mainreporoot=$TESTTMP
176 $TESTTMP/hgrc:11: defaults.identify=-n
176 $TESTTMP/hgrc:11: defaults.identify=-n
177 $TESTTMP/hgrc:2: ui.debug=true
177 $TESTTMP/hgrc:2: ui.debug=true
178 $TESTTMP/hgrc:3: ui.fallbackencoding=ASCII
178 $TESTTMP/hgrc:3: ui.fallbackencoding=ASCII
179 $TESTTMP/hgrc:4: ui.quiet=true
179 $TESTTMP/hgrc:4: ui.quiet=true
180 $TESTTMP/hgrc:5: ui.slash=true
180 $TESTTMP/hgrc:5: ui.slash=true
181 $TESTTMP/hgrc:6: ui.traceback=true
181 $TESTTMP/hgrc:6: ui.traceback=true
182 $TESTTMP/hgrc:7: ui.verbose=true
182 $TESTTMP/hgrc:7: ui.verbose=true
183 $TESTTMP/hgrc:8: ui.style=~/.hgstyle
183 $TESTTMP/hgrc:8: ui.style=~/.hgstyle
184 $TESTTMP/hgrc:9: ui.logtemplate={node}
184 $TESTTMP/hgrc:9: ui.logtemplate={node}
185
185
186 plain hgrc
186 plain hgrc
187
187
188 $ HGPLAIN=; export HGPLAIN
188 $ HGPLAIN=; export HGPLAIN
189 $ hg showconfig --config ui.traceback=True --debug
189 $ hg showconfig --config ui.traceback=True --debug
190 read config from: $TESTTMP/hgrc
190 read config from: $TESTTMP/hgrc
191 repo: bundle.mainreporoot=$TESTTMP
191 repo: bundle.mainreporoot=$TESTTMP
192 --config: ui.traceback=True
192 --config: ui.traceback=True
193 --verbose: ui.verbose=False
193 --verbose: ui.verbose=False
194 --debug: ui.debug=True
194 --debug: ui.debug=True
195 --quiet: ui.quiet=False
195 --quiet: ui.quiet=False
196
196
197 with environment variables
197 with environment variables
198
198
199 $ PAGER=p1 EDITOR=e1 VISUAL=e2 hg showconfig --debug
199 $ PAGER=p1 EDITOR=e1 VISUAL=e2 hg showconfig --debug
200 read config from: $TESTTMP/hgrc
200 read config from: $TESTTMP/hgrc
201 repo: bundle.mainreporoot=$TESTTMP
201 repo: bundle.mainreporoot=$TESTTMP
202 $PAGER: pager.pager=p1
202 $PAGER: pager.pager=p1
203 $VISUAL: ui.editor=e2
203 $VISUAL: ui.editor=e2
204 --verbose: ui.verbose=False
204 --verbose: ui.verbose=False
205 --debug: ui.debug=True
205 --debug: ui.debug=True
206 --quiet: ui.quiet=False
206 --quiet: ui.quiet=False
207
207
208 plain mode with exceptions
208 plain mode with exceptions
209
209
210 $ cat > plain.py <<EOF
210 $ cat > plain.py <<EOF
211 > from mercurial import commands, extensions
211 > from mercurial import commands, extensions
212 > def _config(orig, ui, repo, *values, **opts):
212 > def _config(orig, ui, repo, *values, **opts):
213 > ui.write(b'plain: %r\n' % ui.plain())
213 > ui.write(b'plain: %r\n' % ui.plain())
214 > return orig(ui, repo, *values, **opts)
214 > return orig(ui, repo, *values, **opts)
215 > def uisetup(ui):
215 > def uisetup(ui):
216 > extensions.wrapcommand(commands.table, b'config', _config)
216 > extensions.wrapcommand(commands.table, b'config', _config)
217 > EOF
217 > EOF
218 $ echo "[extensions]" >> $HGRC
218 $ echo "[extensions]" >> $HGRC
219 $ echo "plain=./plain.py" >> $HGRC
219 $ echo "plain=./plain.py" >> $HGRC
220 $ HGPLAINEXCEPT=; export HGPLAINEXCEPT
220 $ HGPLAINEXCEPT=; export HGPLAINEXCEPT
221 $ hg showconfig --config ui.traceback=True --debug
221 $ hg showconfig --config ui.traceback=True --debug
222 plain: True
222 plain: True
223 read config from: $TESTTMP/hgrc
223 read config from: $TESTTMP/hgrc
224 repo: bundle.mainreporoot=$TESTTMP
224 repo: bundle.mainreporoot=$TESTTMP
225 $TESTTMP/hgrc:15: extensions.plain=./plain.py
225 $TESTTMP/hgrc:15: extensions.plain=./plain.py
226 --config: ui.traceback=True
226 --config: ui.traceback=True
227 --verbose: ui.verbose=False
227 --verbose: ui.verbose=False
228 --debug: ui.debug=True
228 --debug: ui.debug=True
229 --quiet: ui.quiet=False
229 --quiet: ui.quiet=False
230 $ unset HGPLAIN
230 $ unset HGPLAIN
231 $ hg showconfig --config ui.traceback=True --debug
231 $ hg showconfig --config ui.traceback=True --debug
232 plain: True
232 plain: True
233 read config from: $TESTTMP/hgrc
233 read config from: $TESTTMP/hgrc
234 repo: bundle.mainreporoot=$TESTTMP
234 repo: bundle.mainreporoot=$TESTTMP
235 $TESTTMP/hgrc:15: extensions.plain=./plain.py
235 $TESTTMP/hgrc:15: extensions.plain=./plain.py
236 --config: ui.traceback=True
236 --config: ui.traceback=True
237 --verbose: ui.verbose=False
237 --verbose: ui.verbose=False
238 --debug: ui.debug=True
238 --debug: ui.debug=True
239 --quiet: ui.quiet=False
239 --quiet: ui.quiet=False
240 $ HGPLAINEXCEPT=i18n; export HGPLAINEXCEPT
240 $ HGPLAINEXCEPT=i18n; export HGPLAINEXCEPT
241 $ hg showconfig --config ui.traceback=True --debug
241 $ hg showconfig --config ui.traceback=True --debug
242 plain: True
242 plain: True
243 read config from: $TESTTMP/hgrc
243 read config from: $TESTTMP/hgrc
244 repo: bundle.mainreporoot=$TESTTMP
244 repo: bundle.mainreporoot=$TESTTMP
245 $TESTTMP/hgrc:15: extensions.plain=./plain.py
245 $TESTTMP/hgrc:15: extensions.plain=./plain.py
246 --config: ui.traceback=True
246 --config: ui.traceback=True
247 --verbose: ui.verbose=False
247 --verbose: ui.verbose=False
248 --debug: ui.debug=True
248 --debug: ui.debug=True
249 --quiet: ui.quiet=False
249 --quiet: ui.quiet=False
250
250
251 source of paths is not mangled
251 source of paths is not mangled
252
252
253 $ cat >> $HGRCPATH <<EOF
253 $ cat >> $HGRCPATH <<EOF
254 > [paths]
254 > [paths]
255 > foo = bar
255 > foo = bar
256 > EOF
256 > EOF
257 $ hg showconfig --debug paths
257 $ hg showconfig --debug paths
258 plain: True
258 plain: True
259 read config from: $TESTTMP/hgrc
259 read config from: $TESTTMP/hgrc
260 $TESTTMP/hgrc:17: paths.foo=$TESTTMP/bar
260 $TESTTMP/hgrc:17: paths.foo=$TESTTMP/bar
261
261
262 Test we can skip the user configuration
262 Test we can skip the user configuration
263
263
264 $ cat >> .hg/hgrc <<EOF
264 $ cat >> .hg/hgrc <<EOF
265 > [paths]
265 > [paths]
266 > elephant = babar
266 > elephant = babar
267 > EOF
267 > EOF
268 $ hg path
268 $ hg path
269 elephant = $TESTTMP/babar
269 elephant = $TESTTMP/babar
270 foo = $TESTTMP/bar
270 foo = $TESTTMP/bar
271 $ HGRCSKIPREPO=1 hg path
271 $ HGRCSKIPREPO=1 hg path
272 foo = $TESTTMP/bar
272 foo = $TESTTMP/bar
273
273
274 $ cat >> .hg/hgrc <<EOF
274 $ cat >> .hg/hgrc <<EOF
275 > [broken
275 > [broken
276 > EOF
276 > EOF
277
277
278 $ hg path
278 $ hg path
279 hg: parse error at $TESTTMP/.hg/hgrc:3: [broken
279 hg: parse error at $TESTTMP/.hg/hgrc:3: [broken
280 [255]
280 [255]
281 $ HGRCSKIPREPO=1 hg path
281 $ HGRCSKIPREPO=1 hg path
282 foo = $TESTTMP/bar
282 foo = $TESTTMP/bar
283
283
284 Check that hgweb respect HGRCSKIPREPO=1
285
286 $ hg serve -n test -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
287 hg: parse error at $TESTTMP/.hg/hgrc:3: [broken
288 [255]
289 $ test -f hg.pid && (cat hg.pid >> $DAEMON_PIDS)
290 [1]
291 $ killdaemons.py
292 $ test -f access.log && cat access.log
293 [1]
294 $ test -f errors.log && cat errors.log
295 [1]
296
297 $ HGRCSKIPREPO=1 hg serve -n test -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
298 $ cat hg.pid >> $DAEMON_PIDS
299 $ killdaemons.py
300 $ cat access.log
301 $ cat errors.log
General Comments 0
You need to be logged in to leave comments. Login now