##// END OF EJS Templates
hgwebdir: use web.prefix when creating url breadcrumbs (issue3790)...
Angel Ezquerra -
r18515:bf8bbbf4 stable
parent child Browse files
Show More
@@ -1,334 +1,336
1 1 # hgweb/hgweb_mod.py - Web interface for a repository.
2 2 #
3 3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 5 #
6 6 # This software may be used and distributed according to the terms of the
7 7 # GNU General Public License version 2 or any later version.
8 8
9 9 import os
10 10 from mercurial import ui, hg, hook, error, encoding, templater, util
11 11 from common import get_stat, ErrorResponse, permhooks, caching
12 12 from common import HTTP_OK, HTTP_NOT_MODIFIED, HTTP_BAD_REQUEST
13 13 from common import HTTP_NOT_FOUND, HTTP_SERVER_ERROR
14 14 from request import wsgirequest
15 15 import webcommands, protocol, webutil
16 16
17 17 perms = {
18 18 'changegroup': 'pull',
19 19 'changegroupsubset': 'pull',
20 20 'getbundle': 'pull',
21 21 'stream_out': 'pull',
22 22 'listkeys': 'pull',
23 23 'unbundle': 'push',
24 24 'pushkey': 'push',
25 25 }
26 26
27 def makebreadcrumb(url):
27 def makebreadcrumb(url, prefix=''):
28 28 '''Return a 'URL breadcrumb' list
29 29
30 30 A 'URL breadcrumb' is a list of URL-name pairs,
31 31 corresponding to each of the path items on a URL.
32 32 This can be used to create path navigation entries.
33 33 '''
34 34 if url.endswith('/'):
35 35 url = url[:-1]
36 if prefix:
37 url = '/' + prefix + url
36 38 relpath = url
37 39 if relpath.startswith('/'):
38 40 relpath = relpath[1:]
39 41
40 42 breadcrumb = []
41 43 urlel = url
42 44 pathitems = [''] + relpath.split('/')
43 45 for pathel in reversed(pathitems):
44 46 if not pathel or not urlel:
45 47 break
46 48 breadcrumb.append({'url': urlel, 'name': pathel})
47 49 urlel = os.path.dirname(urlel)
48 50 return reversed(breadcrumb)
49 51
50 52
51 53 class hgweb(object):
52 54 def __init__(self, repo, name=None, baseui=None):
53 55 if isinstance(repo, str):
54 56 if baseui:
55 57 u = baseui.copy()
56 58 else:
57 59 u = ui.ui()
58 60 self.repo = hg.repository(u, repo)
59 61 else:
60 62 self.repo = repo
61 63
62 64 self.repo = self.repo.filtered('served')
63 65 self.repo.ui.setconfig('ui', 'report_untrusted', 'off')
64 66 self.repo.ui.setconfig('ui', 'nontty', 'true')
65 67 hook.redirect(True)
66 68 self.mtime = -1
67 69 self.size = -1
68 70 self.reponame = name
69 71 self.archives = 'zip', 'gz', 'bz2'
70 72 self.stripecount = 1
71 73 # a repo owner may set web.templates in .hg/hgrc to get any file
72 74 # readable by the user running the CGI script
73 75 self.templatepath = self.config('web', 'templates')
74 76
75 77 # The CGI scripts are often run by a user different from the repo owner.
76 78 # Trust the settings from the .hg/hgrc files by default.
77 79 def config(self, section, name, default=None, untrusted=True):
78 80 return self.repo.ui.config(section, name, default,
79 81 untrusted=untrusted)
80 82
81 83 def configbool(self, section, name, default=False, untrusted=True):
82 84 return self.repo.ui.configbool(section, name, default,
83 85 untrusted=untrusted)
84 86
85 87 def configlist(self, section, name, default=None, untrusted=True):
86 88 return self.repo.ui.configlist(section, name, default,
87 89 untrusted=untrusted)
88 90
89 91 def refresh(self, request=None):
90 92 if request:
91 93 self.repo.ui.environ = request.env
92 94 st = get_stat(self.repo.spath)
93 95 # compare changelog size in addition to mtime to catch
94 96 # rollbacks made less than a second ago
95 97 if st.st_mtime != self.mtime or st.st_size != self.size:
96 98 self.mtime = st.st_mtime
97 99 self.size = st.st_size
98 100 self.repo = hg.repository(self.repo.ui, self.repo.root)
99 101 self.repo = self.repo.filtered('served')
100 102 self.maxchanges = int(self.config("web", "maxchanges", 10))
101 103 self.stripecount = int(self.config("web", "stripes", 1))
102 104 self.maxshortchanges = int(self.config("web", "maxshortchanges",
103 105 60))
104 106 self.maxfiles = int(self.config("web", "maxfiles", 10))
105 107 self.allowpull = self.configbool("web", "allowpull", True)
106 108 encoding.encoding = self.config("web", "encoding",
107 109 encoding.encoding)
108 110
109 111 def run(self):
110 112 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
111 113 raise RuntimeError("This function is only intended to be "
112 114 "called while running as a CGI script.")
113 115 import mercurial.hgweb.wsgicgi as wsgicgi
114 116 wsgicgi.launch(self)
115 117
116 118 def __call__(self, env, respond):
117 119 req = wsgirequest(env, respond)
118 120 return self.run_wsgi(req)
119 121
120 122 def run_wsgi(self, req):
121 123
122 124 self.refresh(req)
123 125
124 126 # work with CGI variables to create coherent structure
125 127 # use SCRIPT_NAME, PATH_INFO and QUERY_STRING as well as our REPO_NAME
126 128
127 129 req.url = req.env['SCRIPT_NAME']
128 130 if not req.url.endswith('/'):
129 131 req.url += '/'
130 132 if 'REPO_NAME' in req.env:
131 133 req.url += req.env['REPO_NAME'] + '/'
132 134
133 135 if 'PATH_INFO' in req.env:
134 136 parts = req.env['PATH_INFO'].strip('/').split('/')
135 137 repo_parts = req.env.get('REPO_NAME', '').split('/')
136 138 if parts[:len(repo_parts)] == repo_parts:
137 139 parts = parts[len(repo_parts):]
138 140 query = '/'.join(parts)
139 141 else:
140 142 query = req.env['QUERY_STRING'].split('&', 1)[0]
141 143 query = query.split(';', 1)[0]
142 144
143 145 # process this if it's a protocol request
144 146 # protocol bits don't need to create any URLs
145 147 # and the clients always use the old URL structure
146 148
147 149 cmd = req.form.get('cmd', [''])[0]
148 150 if protocol.iscmd(cmd):
149 151 try:
150 152 if query:
151 153 raise ErrorResponse(HTTP_NOT_FOUND)
152 154 if cmd in perms:
153 155 self.check_perm(req, perms[cmd])
154 156 return protocol.call(self.repo, req, cmd)
155 157 except ErrorResponse, inst:
156 158 # A client that sends unbundle without 100-continue will
157 159 # break if we respond early.
158 160 if (cmd == 'unbundle' and
159 161 (req.env.get('HTTP_EXPECT',
160 162 '').lower() != '100-continue') or
161 163 req.env.get('X-HgHttp2', '')):
162 164 req.drain()
163 165 req.respond(inst, protocol.HGTYPE,
164 166 body='0\n%s\n' % inst.message)
165 167 return ''
166 168
167 169 # translate user-visible url structure to internal structure
168 170
169 171 args = query.split('/', 2)
170 172 if 'cmd' not in req.form and args and args[0]:
171 173
172 174 cmd = args.pop(0)
173 175 style = cmd.rfind('-')
174 176 if style != -1:
175 177 req.form['style'] = [cmd[:style]]
176 178 cmd = cmd[style + 1:]
177 179
178 180 # avoid accepting e.g. style parameter as command
179 181 if util.safehasattr(webcommands, cmd):
180 182 req.form['cmd'] = [cmd]
181 183 else:
182 184 cmd = ''
183 185
184 186 if cmd == 'static':
185 187 req.form['file'] = ['/'.join(args)]
186 188 else:
187 189 if args and args[0]:
188 190 node = args.pop(0)
189 191 req.form['node'] = [node]
190 192 if args:
191 193 req.form['file'] = args
192 194
193 195 ua = req.env.get('HTTP_USER_AGENT', '')
194 196 if cmd == 'rev' and 'mercurial' in ua:
195 197 req.form['style'] = ['raw']
196 198
197 199 if cmd == 'archive':
198 200 fn = req.form['node'][0]
199 201 for type_, spec in self.archive_specs.iteritems():
200 202 ext = spec[2]
201 203 if fn.endswith(ext):
202 204 req.form['node'] = [fn[:-len(ext)]]
203 205 req.form['type'] = [type_]
204 206
205 207 # process the web interface request
206 208
207 209 try:
208 210 tmpl = self.templater(req)
209 211 ctype = tmpl('mimetype', encoding=encoding.encoding)
210 212 ctype = templater.stringify(ctype)
211 213
212 214 # check read permissions non-static content
213 215 if cmd != 'static':
214 216 self.check_perm(req, None)
215 217
216 218 if cmd == '':
217 219 req.form['cmd'] = [tmpl.cache['default']]
218 220 cmd = req.form['cmd'][0]
219 221
220 222 if self.configbool('web', 'cache', True):
221 223 caching(self, req) # sets ETag header or raises NOT_MODIFIED
222 224 if cmd not in webcommands.__all__:
223 225 msg = 'no such method: %s' % cmd
224 226 raise ErrorResponse(HTTP_BAD_REQUEST, msg)
225 227 elif cmd == 'file' and 'raw' in req.form.get('style', []):
226 228 self.ctype = ctype
227 229 content = webcommands.rawfile(self, req, tmpl)
228 230 else:
229 231 content = getattr(webcommands, cmd)(self, req, tmpl)
230 232 req.respond(HTTP_OK, ctype)
231 233
232 234 return content
233 235
234 236 except error.LookupError, err:
235 237 req.respond(HTTP_NOT_FOUND, ctype)
236 238 msg = str(err)
237 239 if 'manifest' not in msg:
238 240 msg = 'revision not found: %s' % err.name
239 241 return tmpl('error', error=msg)
240 242 except (error.RepoError, error.RevlogError), inst:
241 243 req.respond(HTTP_SERVER_ERROR, ctype)
242 244 return tmpl('error', error=str(inst))
243 245 except ErrorResponse, inst:
244 246 req.respond(inst, ctype)
245 247 if inst.code == HTTP_NOT_MODIFIED:
246 248 # Not allowed to return a body on a 304
247 249 return ['']
248 250 return tmpl('error', error=inst.message)
249 251
250 252 def templater(self, req):
251 253
252 254 # determine scheme, port and server name
253 255 # this is needed to create absolute urls
254 256
255 257 proto = req.env.get('wsgi.url_scheme')
256 258 if proto == 'https':
257 259 proto = 'https'
258 260 default_port = "443"
259 261 else:
260 262 proto = 'http'
261 263 default_port = "80"
262 264
263 265 port = req.env["SERVER_PORT"]
264 266 port = port != default_port and (":" + port) or ""
265 267 urlbase = '%s://%s%s' % (proto, req.env['SERVER_NAME'], port)
266 268 logourl = self.config("web", "logourl", "http://mercurial.selenic.com/")
267 269 logoimg = self.config("web", "logoimg", "hglogo.png")
268 270 staticurl = self.config("web", "staticurl") or req.url + 'static/'
269 271 if not staticurl.endswith('/'):
270 272 staticurl += '/'
271 273
272 274 # some functions for the templater
273 275
274 276 def header(**map):
275 277 yield tmpl('header', encoding=encoding.encoding, **map)
276 278
277 279 def footer(**map):
278 280 yield tmpl("footer", **map)
279 281
280 282 def motd(**map):
281 283 yield self.config("web", "motd", "")
282 284
283 285 # figure out which style to use
284 286
285 287 vars = {}
286 288 styles = (
287 289 req.form.get('style', [None])[0],
288 290 self.config('web', 'style'),
289 291 'paper',
290 292 )
291 293 style, mapfile = templater.stylemap(styles, self.templatepath)
292 294 if style == styles[0]:
293 295 vars['style'] = style
294 296
295 297 start = req.url[-1] == '?' and '&' or '?'
296 298 sessionvars = webutil.sessionvars(vars, start)
297 299
298 300 if not self.reponame:
299 301 self.reponame = (self.config("web", "name")
300 302 or req.env.get('REPO_NAME')
301 303 or req.url.strip('/') or self.repo.root)
302 304
303 305 # create the templater
304 306
305 307 tmpl = templater.templater(mapfile,
306 308 defaults={"url": req.url,
307 309 "logourl": logourl,
308 310 "logoimg": logoimg,
309 311 "staticurl": staticurl,
310 312 "urlbase": urlbase,
311 313 "repo": self.reponame,
312 314 "header": header,
313 315 "footer": footer,
314 316 "motd": motd,
315 317 "sessionvars": sessionvars,
316 318 "pathdef": makebreadcrumb(req.url),
317 319 })
318 320 return tmpl
319 321
320 322 def archivelist(self, nodeid):
321 323 allowed = self.configlist("web", "allow_archive")
322 324 for i, spec in self.archive_specs.iteritems():
323 325 if i in allowed or self.configbool("web", "allow" + i):
324 326 yield {"type" : i, "extension" : spec[2], "node" : nodeid}
325 327
326 328 archive_specs = {
327 329 'bz2': ('application/x-bzip2', 'tbz2', '.tar.bz2', None),
328 330 'gz': ('application/x-gzip', 'tgz', '.tar.gz', None),
329 331 'zip': ('application/zip', 'zip', '.zip', None),
330 332 }
331 333
332 334 def check_perm(self, req, op):
333 335 for hook in permhooks:
334 336 hook(self, req, op)
@@ -1,459 +1,465
1 1 # hgweb/hgwebdir_mod.py - Web interface for a directory of repositories.
2 2 #
3 3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 4 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
5 5 #
6 6 # This software may be used and distributed according to the terms of the
7 7 # GNU General Public License version 2 or any later version.
8 8
9 9 import os, re, time
10 10 from mercurial.i18n import _
11 11 from mercurial import ui, hg, scmutil, util, templater
12 12 from mercurial import error, encoding
13 13 from common import ErrorResponse, get_mtime, staticfile, paritygen, \
14 14 get_contact, HTTP_OK, HTTP_NOT_FOUND, HTTP_SERVER_ERROR
15 15 from hgweb_mod import hgweb, makebreadcrumb
16 16 from request import wsgirequest
17 17 import webutil
18 18
19 19 def cleannames(items):
20 20 return [(util.pconvert(name).strip('/'), path) for name, path in items]
21 21
22 22 def findrepos(paths):
23 23 repos = []
24 24 for prefix, root in cleannames(paths):
25 25 roothead, roottail = os.path.split(root)
26 26 # "foo = /bar/*" or "foo = /bar/**" lets every repo /bar/N in or below
27 27 # /bar/ be served as as foo/N .
28 28 # '*' will not search inside dirs with .hg (except .hg/patches),
29 29 # '**' will search inside dirs with .hg (and thus also find subrepos).
30 30 try:
31 31 recurse = {'*': False, '**': True}[roottail]
32 32 except KeyError:
33 33 repos.append((prefix, root))
34 34 continue
35 35 roothead = os.path.normpath(os.path.abspath(roothead))
36 36 paths = scmutil.walkrepos(roothead, followsym=True, recurse=recurse)
37 37 repos.extend(urlrepos(prefix, roothead, paths))
38 38 return repos
39 39
40 40 def urlrepos(prefix, roothead, paths):
41 41 """yield url paths and filesystem paths from a list of repo paths
42 42
43 43 >>> conv = lambda seq: [(v, util.pconvert(p)) for v,p in seq]
44 44 >>> conv(urlrepos('hg', '/opt', ['/opt/r', '/opt/r/r', '/opt']))
45 45 [('hg/r', '/opt/r'), ('hg/r/r', '/opt/r/r'), ('hg', '/opt')]
46 46 >>> conv(urlrepos('', '/opt', ['/opt/r', '/opt/r/r', '/opt']))
47 47 [('r', '/opt/r'), ('r/r', '/opt/r/r'), ('', '/opt')]
48 48 """
49 49 for path in paths:
50 50 path = os.path.normpath(path)
51 51 yield (prefix + '/' +
52 52 util.pconvert(path[len(roothead):]).lstrip('/')).strip('/'), path
53 53
54 54 def geturlcgivars(baseurl, port):
55 55 """
56 56 Extract CGI variables from baseurl
57 57
58 58 >>> geturlcgivars("http://host.org/base", "80")
59 59 ('host.org', '80', '/base')
60 60 >>> geturlcgivars("http://host.org:8000/base", "80")
61 61 ('host.org', '8000', '/base')
62 62 >>> geturlcgivars('/base', 8000)
63 63 ('', '8000', '/base')
64 64 >>> geturlcgivars("base", '8000')
65 65 ('', '8000', '/base')
66 66 >>> geturlcgivars("http://host", '8000')
67 67 ('host', '8000', '/')
68 68 >>> geturlcgivars("http://host/", '8000')
69 69 ('host', '8000', '/')
70 70 """
71 71 u = util.url(baseurl)
72 72 name = u.host or ''
73 73 if u.port:
74 74 port = u.port
75 75 path = u.path or ""
76 76 if not path.startswith('/'):
77 77 path = '/' + path
78 78
79 79 return name, str(port), path
80 80
81 81 class hgwebdir(object):
82 82 refreshinterval = 20
83 83
84 84 def __init__(self, conf, baseui=None):
85 85 self.conf = conf
86 86 self.baseui = baseui
87 87 self.lastrefresh = 0
88 88 self.motd = None
89 89 self.refresh()
90 90
91 91 def refresh(self):
92 92 if self.lastrefresh + self.refreshinterval > time.time():
93 93 return
94 94
95 95 if self.baseui:
96 96 u = self.baseui.copy()
97 97 else:
98 98 u = ui.ui()
99 99 u.setconfig('ui', 'report_untrusted', 'off')
100 100 u.setconfig('ui', 'nontty', 'true')
101 101
102 102 if not isinstance(self.conf, (dict, list, tuple)):
103 103 map = {'paths': 'hgweb-paths'}
104 104 if not os.path.exists(self.conf):
105 105 raise util.Abort(_('config file %s not found!') % self.conf)
106 106 u.readconfig(self.conf, remap=map, trust=True)
107 107 paths = []
108 108 for name, ignored in u.configitems('hgweb-paths'):
109 109 for path in u.configlist('hgweb-paths', name):
110 110 paths.append((name, path))
111 111 elif isinstance(self.conf, (list, tuple)):
112 112 paths = self.conf
113 113 elif isinstance(self.conf, dict):
114 114 paths = self.conf.items()
115 115
116 116 repos = findrepos(paths)
117 117 for prefix, root in u.configitems('collections'):
118 118 prefix = util.pconvert(prefix)
119 119 for path in scmutil.walkrepos(root, followsym=True):
120 120 repo = os.path.normpath(path)
121 121 name = util.pconvert(repo)
122 122 if name.startswith(prefix):
123 123 name = name[len(prefix):]
124 124 repos.append((name.lstrip('/'), repo))
125 125
126 126 self.repos = repos
127 127 self.ui = u
128 128 encoding.encoding = self.ui.config('web', 'encoding',
129 129 encoding.encoding)
130 130 self.style = self.ui.config('web', 'style', 'paper')
131 131 self.templatepath = self.ui.config('web', 'templates', None)
132 132 self.stripecount = self.ui.config('web', 'stripes', 1)
133 133 if self.stripecount:
134 134 self.stripecount = int(self.stripecount)
135 135 self._baseurl = self.ui.config('web', 'baseurl')
136 prefix = self.ui.config('web', 'prefix', '')
137 if prefix.startswith('/'):
138 prefix = prefix[1:]
139 if prefix.endswith('/'):
140 prefix = prefix[:-1]
141 self.prefix = prefix
136 142 self.lastrefresh = time.time()
137 143
138 144 def run(self):
139 145 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
140 146 raise RuntimeError("This function is only intended to be "
141 147 "called while running as a CGI script.")
142 148 import mercurial.hgweb.wsgicgi as wsgicgi
143 149 wsgicgi.launch(self)
144 150
145 151 def __call__(self, env, respond):
146 152 req = wsgirequest(env, respond)
147 153 return self.run_wsgi(req)
148 154
149 155 def read_allowed(self, ui, req):
150 156 """Check allow_read and deny_read config options of a repo's ui object
151 157 to determine user permissions. By default, with neither option set (or
152 158 both empty), allow all users to read the repo. There are two ways a
153 159 user can be denied read access: (1) deny_read is not empty, and the
154 160 user is unauthenticated or deny_read contains user (or *), and (2)
155 161 allow_read is not empty and the user is not in allow_read. Return True
156 162 if user is allowed to read the repo, else return False."""
157 163
158 164 user = req.env.get('REMOTE_USER')
159 165
160 166 deny_read = ui.configlist('web', 'deny_read', untrusted=True)
161 167 if deny_read and (not user or deny_read == ['*'] or user in deny_read):
162 168 return False
163 169
164 170 allow_read = ui.configlist('web', 'allow_read', untrusted=True)
165 171 # by default, allow reading if no allow_read option has been set
166 172 if (not allow_read) or (allow_read == ['*']) or (user in allow_read):
167 173 return True
168 174
169 175 return False
170 176
171 177 def run_wsgi(self, req):
172 178 try:
173 179 try:
174 180 self.refresh()
175 181
176 182 virtual = req.env.get("PATH_INFO", "").strip('/')
177 183 tmpl = self.templater(req)
178 184 ctype = tmpl('mimetype', encoding=encoding.encoding)
179 185 ctype = templater.stringify(ctype)
180 186
181 187 # a static file
182 188 if virtual.startswith('static/') or 'static' in req.form:
183 189 if virtual.startswith('static/'):
184 190 fname = virtual[7:]
185 191 else:
186 192 fname = req.form['static'][0]
187 193 static = self.ui.config("web", "static", None,
188 194 untrusted=False)
189 195 if not static:
190 196 tp = self.templatepath or templater.templatepath()
191 197 if isinstance(tp, str):
192 198 tp = [tp]
193 199 static = [os.path.join(p, 'static') for p in tp]
194 200 return (staticfile(static, fname, req),)
195 201
196 202 # top-level index
197 203 elif not virtual:
198 204 req.respond(HTTP_OK, ctype)
199 205 return self.makeindex(req, tmpl)
200 206
201 207 # nested indexes and hgwebs
202 208
203 209 repos = dict(self.repos)
204 210 virtualrepo = virtual
205 211 while virtualrepo:
206 212 real = repos.get(virtualrepo)
207 213 if real:
208 214 req.env['REPO_NAME'] = virtualrepo
209 215 try:
210 216 repo = hg.repository(self.ui, real)
211 217 return hgweb(repo).run_wsgi(req)
212 218 except IOError, inst:
213 219 msg = inst.strerror
214 220 raise ErrorResponse(HTTP_SERVER_ERROR, msg)
215 221 except error.RepoError, inst:
216 222 raise ErrorResponse(HTTP_SERVER_ERROR, str(inst))
217 223
218 224 up = virtualrepo.rfind('/')
219 225 if up < 0:
220 226 break
221 227 virtualrepo = virtualrepo[:up]
222 228
223 229 # browse subdirectories
224 230 subdir = virtual + '/'
225 231 if [r for r in repos if r.startswith(subdir)]:
226 232 req.respond(HTTP_OK, ctype)
227 233 return self.makeindex(req, tmpl, subdir)
228 234
229 235 # prefixes not found
230 236 req.respond(HTTP_NOT_FOUND, ctype)
231 237 return tmpl("notfound", repo=virtual)
232 238
233 239 except ErrorResponse, err:
234 240 req.respond(err, ctype)
235 241 return tmpl('error', error=err.message or '')
236 242 finally:
237 243 tmpl = None
238 244
239 245 def makeindex(self, req, tmpl, subdir=""):
240 246
241 247 def archivelist(ui, nodeid, url):
242 248 allowed = ui.configlist("web", "allow_archive", untrusted=True)
243 249 archives = []
244 250 for i in [('zip', '.zip'), ('gz', '.tar.gz'), ('bz2', '.tar.bz2')]:
245 251 if i[0] in allowed or ui.configbool("web", "allow" + i[0],
246 252 untrusted=True):
247 253 archives.append({"type" : i[0], "extension": i[1],
248 254 "node": nodeid, "url": url})
249 255 return archives
250 256
251 257 def rawentries(subdir="", **map):
252 258
253 259 descend = self.ui.configbool('web', 'descend', True)
254 260 collapse = self.ui.configbool('web', 'collapse', False)
255 261 seenrepos = set()
256 262 seendirs = set()
257 263 for name, path in self.repos:
258 264
259 265 if not name.startswith(subdir):
260 266 continue
261 267 name = name[len(subdir):]
262 268 directory = False
263 269
264 270 if '/' in name:
265 271 if not descend:
266 272 continue
267 273
268 274 nameparts = name.split('/')
269 275 rootname = nameparts[0]
270 276
271 277 if not collapse:
272 278 pass
273 279 elif rootname in seendirs:
274 280 continue
275 281 elif rootname in seenrepos:
276 282 pass
277 283 else:
278 284 directory = True
279 285 name = rootname
280 286
281 287 # redefine the path to refer to the directory
282 288 discarded = '/'.join(nameparts[1:])
283 289
284 290 # remove name parts plus accompanying slash
285 291 path = path[:-len(discarded) - 1]
286 292
287 293 parts = [name]
288 294 if 'PATH_INFO' in req.env:
289 295 parts.insert(0, req.env['PATH_INFO'].rstrip('/'))
290 296 if req.env['SCRIPT_NAME']:
291 297 parts.insert(0, req.env['SCRIPT_NAME'])
292 298 url = re.sub(r'/+', '/', '/'.join(parts) + '/')
293 299
294 300 # show either a directory entry or a repository
295 301 if directory:
296 302 # get the directory's time information
297 303 try:
298 304 d = (get_mtime(path), util.makedate()[1])
299 305 except OSError:
300 306 continue
301 307
302 308 # add '/' to the name to make it obvious that
303 309 # the entry is a directory, not a regular repository
304 310 row = dict(contact="",
305 311 contact_sort="",
306 312 name=name + '/',
307 313 name_sort=name,
308 314 url=url,
309 315 description="",
310 316 description_sort="",
311 317 lastchange=d,
312 318 lastchange_sort=d[1]-d[0],
313 319 archives=[],
314 320 isdirectory=True)
315 321
316 322 seendirs.add(name)
317 323 yield row
318 324 continue
319 325
320 326 u = self.ui.copy()
321 327 try:
322 328 u.readconfig(os.path.join(path, '.hg', 'hgrc'))
323 329 except Exception, e:
324 330 u.warn(_('error reading %s/.hg/hgrc: %s\n') % (path, e))
325 331 continue
326 332 def get(section, name, default=None):
327 333 return u.config(section, name, default, untrusted=True)
328 334
329 335 if u.configbool("web", "hidden", untrusted=True):
330 336 continue
331 337
332 338 if not self.read_allowed(u, req):
333 339 continue
334 340
335 341 # update time with local timezone
336 342 try:
337 343 r = hg.repository(self.ui, path)
338 344 except IOError:
339 345 u.warn(_('error accessing repository at %s\n') % path)
340 346 continue
341 347 except error.RepoError:
342 348 u.warn(_('error accessing repository at %s\n') % path)
343 349 continue
344 350 try:
345 351 d = (get_mtime(r.spath), util.makedate()[1])
346 352 except OSError:
347 353 continue
348 354
349 355 contact = get_contact(get)
350 356 description = get("web", "description", "")
351 357 name = get("web", "name", name)
352 358 row = dict(contact=contact or "unknown",
353 359 contact_sort=contact.upper() or "unknown",
354 360 name=name,
355 361 name_sort=name,
356 362 url=url,
357 363 description=description or "unknown",
358 364 description_sort=description.upper() or "unknown",
359 365 lastchange=d,
360 366 lastchange_sort=d[1]-d[0],
361 367 archives=archivelist(u, "tip", url))
362 368
363 369 seenrepos.add(name)
364 370 yield row
365 371
366 372 sortdefault = None, False
367 373 def entries(sortcolumn="", descending=False, subdir="", **map):
368 374 rows = rawentries(subdir=subdir, **map)
369 375
370 376 if sortcolumn and sortdefault != (sortcolumn, descending):
371 377 sortkey = '%s_sort' % sortcolumn
372 378 rows = sorted(rows, key=lambda x: x[sortkey],
373 379 reverse=descending)
374 380 for row, parity in zip(rows, paritygen(self.stripecount)):
375 381 row['parity'] = parity
376 382 yield row
377 383
378 384 self.refresh()
379 385 sortable = ["name", "description", "contact", "lastchange"]
380 386 sortcolumn, descending = sortdefault
381 387 if 'sort' in req.form:
382 388 sortcolumn = req.form['sort'][0]
383 389 descending = sortcolumn.startswith('-')
384 390 if descending:
385 391 sortcolumn = sortcolumn[1:]
386 392 if sortcolumn not in sortable:
387 393 sortcolumn = ""
388 394
389 395 sort = [("sort_%s" % column,
390 396 "%s%s" % ((not descending and column == sortcolumn)
391 397 and "-" or "", column))
392 398 for column in sortable]
393 399
394 400 self.refresh()
395 401 self.updatereqenv(req.env)
396 402
397 403 return tmpl("index", entries=entries, subdir=subdir,
398 pathdef=makebreadcrumb('/' + subdir),
404 pathdef=makebreadcrumb('/' + subdir, self.prefix),
399 405 sortcolumn=sortcolumn, descending=descending,
400 406 **dict(sort))
401 407
402 408 def templater(self, req):
403 409
404 410 def header(**map):
405 411 yield tmpl('header', encoding=encoding.encoding, **map)
406 412
407 413 def footer(**map):
408 414 yield tmpl("footer", **map)
409 415
410 416 def motd(**map):
411 417 if self.motd is not None:
412 418 yield self.motd
413 419 else:
414 420 yield config('web', 'motd', '')
415 421
416 422 def config(section, name, default=None, untrusted=True):
417 423 return self.ui.config(section, name, default, untrusted)
418 424
419 425 self.updatereqenv(req.env)
420 426
421 427 url = req.env.get('SCRIPT_NAME', '')
422 428 if not url.endswith('/'):
423 429 url += '/'
424 430
425 431 vars = {}
426 432 styles = (
427 433 req.form.get('style', [None])[0],
428 434 config('web', 'style'),
429 435 'paper'
430 436 )
431 437 style, mapfile = templater.stylemap(styles, self.templatepath)
432 438 if style == styles[0]:
433 439 vars['style'] = style
434 440
435 441 start = url[-1] == '?' and '&' or '?'
436 442 sessionvars = webutil.sessionvars(vars, start)
437 443 logourl = config('web', 'logourl', 'http://mercurial.selenic.com/')
438 444 logoimg = config('web', 'logoimg', 'hglogo.png')
439 445 staticurl = config('web', 'staticurl') or url + 'static/'
440 446 if not staticurl.endswith('/'):
441 447 staticurl += '/'
442 448
443 449 tmpl = templater.templater(mapfile,
444 450 defaults={"header": header,
445 451 "footer": footer,
446 452 "motd": motd,
447 453 "url": url,
448 454 "logourl": logourl,
449 455 "logoimg": logoimg,
450 456 "staticurl": staticurl,
451 457 "sessionvars": sessionvars})
452 458 return tmpl
453 459
454 460 def updatereqenv(self, env):
455 461 if self._baseurl is not None:
456 462 name, port, path = geturlcgivars(self._baseurl, env['SERVER_PORT'])
457 463 env['SERVER_NAME'] = name
458 464 env['SERVER_PORT'] = port
459 465 env['SCRIPT_NAME'] = path
General Comments 0
You need to be logged in to leave comments. Login now